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
File without changes
@@ -0,0 +1,12 @@
1
+ """App config for history."""
2
+
3
+ from django.apps import AppConfig
4
+
5
+
6
+ class HistoryConfig(AppConfig):
7
+ default_auto_field = "django.db.models.BigAutoField"
8
+ name = "trueppm_api.apps.history"
9
+
10
+ def ready(self) -> None:
11
+ # Register the post_save receiver that fires history_record_created.
12
+ import trueppm_api.apps.history.signals # noqa: F401
@@ -0,0 +1,359 @@
1
+ """Unified project changelog aggregator (ADR-0201, #371).
2
+
3
+ A read-only aggregator that merges the project-scoped ``django-simple-history``
4
+ tables into one newest-first "what changed" stream. No new model, no data
5
+ migration — every event already has a durable home in a ``Historical*`` table.
6
+
7
+ Why a Python k-way merge and not a SQL ``UNION``
8
+ ------------------------------------------------
9
+ The nine source tables have heterogeneous columns, so a single ``UNION`` would
10
+ force a lowest-common-denominator projection and is brittle against schema
11
+ drift. Instead each source is fetched ``ORDER BY history_date DESC, history_id
12
+ DESC`` with ``LIMIT page_size + 1`` (a bounded scan — never a query per row),
13
+ and the bounded lists are merge-sorted in Python on the global total-order key.
14
+
15
+ Why a ``(history_date, table_rank, history_id)`` keyset and not ``until``
16
+ ------------------------------------------------------------------------
17
+ ``history_date`` is a timestamp and can collide **across** tables; ``history_id``
18
+ is only unique **within** a table. A timestamp-only cursor (the ``until`` cursor
19
+ of the per-object history and board feeds) is therefore lossy across a cross-table
20
+ tie. The strict total order is the triple ``(history_date DESC, table_rank ASC,
21
+ history_id DESC)``: ``table_rank`` (a fixed integer per source) disambiguates two
22
+ rows in *different* tables sharing an exact ``history_date``, and ``history_id``
23
+ disambiguates two rows in the *same* table. This partitions the global stream
24
+ into pages with no gap and no duplicate — the same guarantee
25
+ ``sync/pagination.py`` documents for ``(server_version, id)``.
26
+
27
+ Permission model (ADR-0201): every source's live GET is ``IsProjectMember``
28
+ (Viewer+), so membership is a sufficient row-inclusion gate — there is no
29
+ per-user sub-project ACL on any source (TimeEntry and the Retrospective models
30
+ carry per-user / team_visibility gates but have **no** ``HistoricalRecords``, so
31
+ they cannot appear here). Field redaction is the shared ``history.diff_policy``
32
+ (one vocabulary for every diff surface, #3435 — it drops ``blocked_reason`` per
33
+ ADR-0124 and the CPM outputs at compute time); ``history_user`` is gated to
34
+ Owner/Admin via the view's ``hide_user`` context, and the ``user=`` filter is
35
+ honored only for those callers.
36
+ """
37
+
38
+ from __future__ import annotations
39
+
40
+ import base64
41
+ import binascii
42
+ import json
43
+ from dataclasses import dataclass
44
+ from datetime import datetime
45
+ from typing import TYPE_CHECKING, Any
46
+
47
+ from django.db.models import Q
48
+ from rest_framework.exceptions import ValidationError
49
+
50
+ if TYPE_CHECKING:
51
+ from django.db.models import QuerySet
52
+
53
+ # django-simple-history ``history_type`` → the changelog's change-type vocabulary.
54
+ _HISTORY_TYPE_TO_CHANGE: dict[str, str] = {"+": "created", "~": "updated", "-": "deleted"}
55
+ _CHANGE_TO_HISTORY_TYPE: dict[str, str] = {v: k for k, v in _HISTORY_TYPE_TO_CHANGE.items()}
56
+
57
+ CHANGE_TYPES: frozenset[str] = frozenset(_CHANGE_TO_HISTORY_TYPE)
58
+
59
+ DEFAULT_PAGE_SIZE = 50
60
+ MAX_PAGE_SIZE = 100
61
+
62
+
63
+ @dataclass(frozen=True)
64
+ class ChangelogSource:
65
+ """One historical table wired into the unified changelog.
66
+
67
+ ``rank`` is the source's fixed position in the total order — it is encoded in
68
+ the cursor and MUST stay stable (reordering the source list would invalidate
69
+ in-flight opaque cursors, which is acceptable for a short-lived pagination
70
+ token but must be a deliberate change).
71
+ """
72
+
73
+ rank: int
74
+ object_type: str
75
+ label_field: str | None # attribute on the historical row to use as the display label
76
+ label_fallback: str # used when label_field is None or the value is empty
77
+ project_filter: str # ORM lookup that scopes the historical rows to a project
78
+
79
+
80
+ def _sources() -> list[ChangelogSource]:
81
+ """The ordered project-scoped historical sources (ADR-0201).
82
+
83
+ Program and CeremonyTemplate are deliberately excluded (program-scoped, not
84
+ project-scoped); Workspace is a singleton. TimeEntry and the Retrospective
85
+ models are excluded because they have no ``HistoricalRecords`` table at all.
86
+ """
87
+ return [
88
+ ChangelogSource(0, "task", "name", "Task", "project_id"),
89
+ ChangelogSource(1, "sprint", "name", "Sprint", "project_id"),
90
+ ChangelogSource(2, "risk", "title", "Risk", "project_id"),
91
+ ChangelogSource(3, "dependency", None, "Dependency", "predecessor__project_id"),
92
+ # HistoricalProject's ``pk`` is ``history_id``; the original project's PK
93
+ # lives in the ``id`` column, so the project's own history is scoped by id.
94
+ ChangelogSource(4, "project", "name", "Project", "id"),
95
+ ChangelogSource(5, "task_recurrence", None, "Recurrence rule", "task__project_id"),
96
+ ChangelogSource(6, "guardrail_policy", None, "Guardrail policy", "project_id"),
97
+ ChangelogSource(7, "signal_privacy_policy", None, "Signal privacy policy", "project_id"),
98
+ ChangelogSource(8, "decisions_policy", None, "Decisions policy", "project_id"),
99
+ ]
100
+
101
+
102
+ def object_type_choices() -> list[str]:
103
+ return [s.object_type for s in _sources()]
104
+
105
+
106
+ def _historical_model(object_type: str) -> Any:
107
+ """Return the ``Historical*`` model class for a source's live model."""
108
+ from trueppm_api.apps.projects.models import (
109
+ Dependency,
110
+ Project,
111
+ ProjectDecisionsPolicy,
112
+ ProjectGuardrailPolicy,
113
+ ProjectSignalPrivacyPolicy,
114
+ Risk,
115
+ Sprint,
116
+ Task,
117
+ TaskRecurrenceRule,
118
+ )
119
+
120
+ live = {
121
+ "task": Task,
122
+ "sprint": Sprint,
123
+ "risk": Risk,
124
+ "dependency": Dependency,
125
+ "project": Project,
126
+ "task_recurrence": TaskRecurrenceRule,
127
+ "guardrail_policy": ProjectGuardrailPolicy,
128
+ "signal_privacy_policy": ProjectSignalPrivacyPolicy,
129
+ "decisions_policy": ProjectDecisionsPolicy,
130
+ }[object_type]
131
+ # ``history`` is the django-simple-history manager, added dynamically to the
132
+ # model class — not visible to the type checker on the VersionedModel base.
133
+ return live.history.model # type: ignore[attr-defined]
134
+
135
+
136
+ @dataclass(frozen=True)
137
+ class ChangelogCursor:
138
+ """Position in the ``(history_date, table_rank, history_id)`` total order.
139
+
140
+ Encoded as an opaque, URL-safe base64 JSON token, mirroring
141
+ :class:`~trueppm_api.apps.sync.pagination.SyncCursor`. The token is
142
+ client-controlled, so every field is validated on decode — a tampered or
143
+ truncated token yields a 400 rather than an unhandled 500.
144
+ """
145
+
146
+ history_date: datetime
147
+ rank: int
148
+ history_id: int
149
+
150
+ def encode(self) -> str:
151
+ raw = json.dumps(
152
+ {"d": self.history_date.isoformat(), "r": self.rank, "id": self.history_id},
153
+ separators=(",", ":"),
154
+ )
155
+ return base64.urlsafe_b64encode(raw.encode()).decode()
156
+
157
+ @classmethod
158
+ def decode(cls, token: str) -> ChangelogCursor:
159
+ from django.utils.dateparse import parse_datetime
160
+
161
+ try:
162
+ raw = base64.urlsafe_b64decode(token.encode())
163
+ data = json.loads(raw)
164
+ parsed = parse_datetime(str(data["d"]))
165
+ if parsed is None:
166
+ raise ValueError("bad date")
167
+ rank = int(data["r"])
168
+ history_id = int(data["id"])
169
+ except (binascii.Error, ValueError, KeyError, TypeError, UnicodeDecodeError) as err:
170
+ raise ValidationError({"cursor": "Malformed pagination cursor."}) from err
171
+ if rank < 0:
172
+ raise ValidationError({"cursor": "Malformed pagination cursor."})
173
+ return cls(history_date=parsed, rank=rank, history_id=history_id)
174
+
175
+
176
+ def _cursor_filter(source: ChangelogSource, cursor: ChangelogCursor) -> Q:
177
+ """The keyset predicate that resumes strictly *after* ``cursor`` for one source.
178
+
179
+ "After" = older in the newest-first total order. With the source's fixed
180
+ ``rank`` known as a constant, the cross-table tie-break collapses to a simple
181
+ per-source predicate (see the module docstring for the derivation):
182
+
183
+ * rank > cursor.rank → every same-``history_date`` row is older → ``<= cd``
184
+ * rank == cursor.rank → tie-break within the table on ``history_id``
185
+ * rank < cursor.rank → same-``history_date`` rows here are newer → already sent
186
+ """
187
+ cd = cursor.history_date
188
+ if source.rank > cursor.rank:
189
+ return Q(history_date__lte=cd)
190
+ if source.rank == cursor.rank:
191
+ return Q(history_date__lt=cd) | Q(history_date=cd, history_id__lt=cursor.history_id)
192
+ return Q(history_date__lt=cd)
193
+
194
+
195
+ def _label_for(source: ChangelogSource, row: Any) -> str:
196
+ if source.label_field is not None:
197
+ value = getattr(row, source.label_field, None)
198
+ if value:
199
+ return str(value)
200
+ return source.label_fallback
201
+
202
+
203
+ def _object_id_for(source: ChangelogSource, row: Any) -> str:
204
+ """The id used for click-through. Policies point at their project (settings);
205
+ every other source points at the object's own row."""
206
+ if source.object_type in {"guardrail_policy", "signal_privacy_policy", "decisions_policy"}:
207
+ return str(row.project_id)
208
+ return str(row.id)
209
+
210
+
211
+ def _fetch_source_rows(
212
+ source: ChangelogSource,
213
+ project: Any,
214
+ *,
215
+ since: datetime | None,
216
+ history_types: set[str] | None,
217
+ user_id: Any,
218
+ cursor: ChangelogCursor | None,
219
+ page_size: int,
220
+ ) -> list[Any]:
221
+ """Fetch one source's bounded, newest-first row window (``LIMIT page_size + 1``).
222
+
223
+ Each of the ``since`` / ``history_types`` / ``user_id`` / ``cursor`` filters is
224
+ applied only when set, so an unconstrained call scans just the project-scoped
225
+ rows. ``select_related("history_user")`` keeps the per-row ``history_user``
226
+ render from fanning out into a query per row.
227
+ """
228
+ model = _historical_model(source.object_type)
229
+ qs: QuerySet[Any] = model.objects.filter(**{source.project_filter: project.pk})
230
+ if since is not None:
231
+ qs = qs.filter(history_date__gte=since)
232
+ if history_types is not None:
233
+ qs = qs.filter(history_type__in=history_types)
234
+ if user_id is not None:
235
+ qs = qs.filter(history_user_id=user_id)
236
+ if cursor is not None:
237
+ qs = qs.filter(_cursor_filter(source, cursor))
238
+ return list(
239
+ qs.select_related("history_user").order_by("-history_date", "-history_id")[: page_size + 1]
240
+ )
241
+
242
+
243
+ def _source_entries(
244
+ source: ChangelogSource,
245
+ rows: list[Any],
246
+ diff_fn: Any,
247
+ ) -> list[tuple[tuple[datetime, int, int], dict[str, Any]]]:
248
+ """Pair one source's rows with their diffs and key each surviving entry.
249
+
250
+ Diffs are computed within this source's batch (same model), so the
251
+ ``history_id``-keyed dict never collides across tables. Rows whose entire diff
252
+ is redacted (:func:`_row_to_entry` returns ``None``) are dropped.
253
+ """
254
+ diffs = diff_fn(rows, all_records=rows)
255
+ collected: list[tuple[tuple[datetime, int, int], dict[str, Any]]] = []
256
+ for row in rows:
257
+ entry = _row_to_entry(source, row, diffs)
258
+ if entry is not None:
259
+ collected.append(((row.history_date, source.rank, row.history_id), entry))
260
+ return collected
261
+
262
+
263
+ def build_project_changelog(
264
+ project: Any,
265
+ *,
266
+ diff_fn: Any,
267
+ cursor: ChangelogCursor | None = None,
268
+ since: datetime | None = None,
269
+ object_types: set[str] | None = None,
270
+ change_types: set[str] | None = None,
271
+ user_id: Any = None,
272
+ page_size: int = DEFAULT_PAGE_SIZE,
273
+ ) -> tuple[list[dict[str, Any]], ChangelogCursor | None]:
274
+ """Build one newest-first page of the unified changelog (ADR-0201).
275
+
276
+ Args:
277
+ project: the project whose history is aggregated.
278
+ diff_fn: ``history.views._compute_diffs`` bound with ``object_scoped=False``
279
+ — injected to avoid an import cycle. It applies the shared
280
+ ``history.diff_policy`` (CPM outputs, sync internals, and
281
+ ``blocked_reason`` per ADR-0124 are never compared; noise is hidden
282
+ unless it is a record's only change) and keys rows by field name.
283
+ cursor: keyset position; ``None`` starts at the newest row.
284
+ since: inclusive lower bound on ``history_date``.
285
+ object_types: restrict to these source ``object_type`` keys (default all).
286
+ change_types: restrict to these change types (``created``/``updated``/``deleted``).
287
+ user_id: restrict to a single ``history_user`` (honored by the caller only
288
+ for Owner/Admin — see the view).
289
+ page_size: rows per page (clamped to ``[1, MAX_PAGE_SIZE]``).
290
+
291
+ Returns:
292
+ ``(entries, next_cursor)``. ``next_cursor`` is ``None`` when the stream is
293
+ exhausted. Each entry is a plain dict — see :func:`_row_to_entry`.
294
+ """
295
+ page_size = max(1, min(int(page_size), MAX_PAGE_SIZE))
296
+ wanted_types = {s.strip() for s in object_types} if object_types else None
297
+ history_types: set[str] | None = (
298
+ {_CHANGE_TO_HISTORY_TYPE[c] for c in change_types} if change_types else None
299
+ )
300
+
301
+ entries: list[tuple[tuple[datetime, int, int], dict[str, Any]]] = []
302
+ for source in _sources():
303
+ if wanted_types is not None and source.object_type not in wanted_types:
304
+ continue
305
+ rows = _fetch_source_rows(
306
+ source,
307
+ project,
308
+ since=since,
309
+ history_types=history_types,
310
+ user_id=user_id,
311
+ cursor=cursor,
312
+ page_size=page_size,
313
+ )
314
+ if rows:
315
+ entries.extend(_source_entries(source, rows, diff_fn))
316
+
317
+ # Newest-first total order: history_date DESC, rank ASC, history_id DESC.
318
+ entries.sort(key=lambda item: (item[0][0], -item[0][1], item[0][2]), reverse=True)
319
+
320
+ has_more = len(entries) > page_size
321
+ page = entries[:page_size]
322
+ next_cursor: ChangelogCursor | None = None
323
+ if has_more and page:
324
+ key = page[-1][0]
325
+ next_cursor = ChangelogCursor(history_date=key[0], rank=key[1], history_id=key[2])
326
+
327
+ return [entry for _, entry in page], next_cursor
328
+
329
+
330
+ def _row_to_entry(
331
+ source: ChangelogSource,
332
+ row: Any,
333
+ diffs: dict[int, list[dict[str, Any]]],
334
+ ) -> dict[str, Any] | None:
335
+ """Turn one historical row into a changelog entry, or ``None`` to drop it.
336
+
337
+ An ``updated`` row whose entire diff is excluded fields (a pure CPM/sync bump)
338
+ carries no team-readable signal and is dropped — matching the per-object
339
+ history views. ``created`` / ``deleted`` rows are always kept (the change type
340
+ is itself the signal) and carry an empty ``changes`` list to keep the feed
341
+ scannable.
342
+ """
343
+ change_type = _HISTORY_TYPE_TO_CHANGE.get(row.history_type, "updated")
344
+ changes: list[dict[str, Any]] = []
345
+ if change_type == "updated":
346
+ changes = diffs.get(row.history_id, [])
347
+ if not changes:
348
+ return None
349
+
350
+ return {
351
+ "id": f"{source.object_type}:{row.history_id}",
352
+ "object_type": source.object_type,
353
+ "object_id": _object_id_for(source, row),
354
+ "object_label": _label_for(source, row),
355
+ "change_type": change_type,
356
+ "history_date": row.history_date,
357
+ "history_user": row.history_user,
358
+ "changes": changes,
359
+ }
@@ -0,0 +1,177 @@
1
+ """Which tracked fields a history diff shows, hides, and promotes — decided once.
2
+
3
+ Task field changes reach users through **three** renderers, and until #3435 each
4
+ carried its own exclusion list:
5
+
6
+ * the task drawer's Activity tab — ``apps/projects/views.py`` (``TaskHistoryView``),
7
+ Task-only, FK values resolved to labels;
8
+ * the project **Activity** page and the per-object history routes —
9
+ ``apps/history/views.py::_compute_diffs``, every project-scoped historical model,
10
+ raw values;
11
+ * the board activity feed — ``apps/projects/board_activity.py``, a curated
12
+ **allow-list** (``_BOARD_DIFF_FIELDS``) rather than allow-by-exclusion.
13
+
14
+ The first two disagreed on what to hide, and only the first had a label map, so the
15
+ project Activity page rendered raw column names for every change the drawer rendered
16
+ as prose. Decision (#3435): the two allow-by-exclusion pipelines **converge** on this
17
+ module. They stay separate functions because they differ in what they *resolve* (the
18
+ drawer batch-resolves FK ids to names; the generic pipeline carries the ids), but
19
+ *which* fields are compared, hidden, and promoted is decided here, and both emit the
20
+ model **field name** (``assignee``), never the column (``assignee_id``), so one client
21
+ label map (``activityFormat.ts::fieldLabel``) serves every surface. The board feed
22
+ keeps its allow-list — a board reader wants six fields, not "everything but the
23
+ noise" — and is pinned against the privacy set by test rather than by sharing code.
24
+
25
+ The sets are grouped by the *reason* a field is hidden, because the reasons carry
26
+ different obligations. A noise exclusion may be promoted back when it is a record's
27
+ only change (#3306); an access-control exclusion never may, and conflating the two
28
+ turns a labelling cleanup into a disclosure bug.
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ from collections.abc import Callable, Iterable
34
+ from typing import TypeVar
35
+
36
+ T = TypeVar("T")
37
+
38
+ # django-simple-history's own columns. Present on every ``Historical*`` row and never
39
+ # a user change; the drawer never sees them because it iterates the live model's
40
+ # fields, but the generic pipeline iterates the historical model's and must drop them.
41
+ HISTORY_DIFF_META_COLUMNS: frozenset[str] = frozenset(
42
+ {"history_id", "history_date", "history_change_reason", "history_user", "history_type"}
43
+ )
44
+
45
+ # Engine outputs and sync counters. ``HistoricalTask`` no longer carries any of these
46
+ # (``_HISTORY_EXCLUDED_TASK`` strips them at tracking time), but the generic pipeline
47
+ # diffs eight other models whose tracking lists differ, so they are named here as well:
48
+ # a model that does track them must still never show a recalculation as an edit.
49
+ HISTORY_DIFF_ENGINE_AND_SYNC: frozenset[str] = frozenset(
50
+ {
51
+ "early_start",
52
+ "early_finish",
53
+ "late_start",
54
+ "late_finish",
55
+ "total_float",
56
+ "free_float",
57
+ "is_critical",
58
+ "scheduled_start",
59
+ "server_version",
60
+ "sync_seq",
61
+ "deleted_version",
62
+ "deleted_at",
63
+ }
64
+ )
65
+
66
+ # Low-signal bookkeeping hidden from every diff surface (ADR-0096 Part 1, #874). These
67
+ # are *display* decisions: the historical model tracks the column, a reader simply
68
+ # gains nothing from a row about it. Members of this set — and only this set — are
69
+ # eligible for promotion when they are a record's only change.
70
+ HISTORY_DIFF_NOISE: frozenset[str] = frozenset(
71
+ {
72
+ "short_id", # immutable, system-assigned identifier
73
+ "status_changed_at", # derived bookkeeping timestamp
74
+ "blocked_since", # derived bookkeeping timestamp
75
+ "sprint_pending", # transient ADR-0102 scope-injection flag
76
+ # sprint-backlog reorder bookkeeping: drag-reorder rewrites the team's
77
+ # within-sprint sequence on every affected sibling, so surfacing it would
78
+ # flood the timeline with integer noise — mirrors the other rank/bookkeeping
79
+ # exclusions (#1885). Deliberate ordering signals (priority_rank) stay visible.
80
+ "sprint_rank",
81
+ # Promoted back into the diff when it is a record's ONLY change — see
82
+ # ``HISTORY_DIFF_PROMOTED_WHEN_ALONE`` below.
83
+ "parent_governance_inherited", # internal inheritance bookkeeping
84
+ "recurrence_occurrence_date", # system-set during recurrence expansion
85
+ "recurrence_rule", # FK to the rule object; recurrence shown via is_recurring
86
+ }
87
+ )
88
+
89
+ # The one deliberate per-surface difference. A soft delete is a ``~`` row whose only
90
+ # tracked change is ``is_deleted`` (``VersionedModel.soft_delete`` saves the flag; a
91
+ # hard ``-`` row exists only for the purge task). On a surface scoped to ONE object —
92
+ # the task drawer — that flag is not a field change worth a row: a deleted task has no
93
+ # drawer to read it in, and a restore re-opens the same drawer to the same task. On the
94
+ # cross-object project Activity page the same row is the ONLY record that the delete
95
+ # happened at all, so hiding it there would make every soft delete invisible — the
96
+ # #3306 "write with no record" shape, for the most consequential write there is.
97
+ HISTORY_DIFF_OBJECT_SCOPED_EXCLUDED: frozenset[str] = frozenset({"is_deleted"})
98
+
99
+ # Excluded for an ACCESS-CONTROL reason, not a noise one, and therefore never
100
+ # promotable and never a per-surface judgment call.
101
+ #
102
+ # ``blocked_reason`` is contributor voice (ADR-0124, #1135 — the "Morgan surveillance
103
+ # boundary"): ``TaskSerializer`` read-gates it to the assignee and @-mentioned users
104
+ # via ``can_read_blocker_reason()``, while every history endpoint is Viewer+ for every
105
+ # project member. ``HistoricalTask`` deliberately still tracks it (it is NOT in
106
+ # ``_HISTORY_EXCLUDED_TASK``), so this exclusion is the only thing keeping the reason
107
+ # — and every past reason — off a team-readable feed. The structured blocker signal
108
+ # (``blocker_type`` / ``blocked_since`` / ``blocked_by`` / ``blocking_task``) is
109
+ # team-shareable and stays in the diff.
110
+ HISTORY_DIFF_PRIVACY_GATED: frozenset[str] = frozenset({"blocked_reason"})
111
+
112
+ # Noise exclusions surfaced anyway when they are the ONLY thing a record changed (#3306).
113
+ #
114
+ # ``parent_governance_inherited`` is genuinely bookkeeping alongside a governance
115
+ # change: every ordinary governance write moves the two together, so surfacing both
116
+ # would double the diff rows on the highest-frequency case and turn the summary verb
117
+ # from "changed governance" into "updated 2 fields". That call stands.
118
+ #
119
+ # But the bit can also move *alone*, and then the exclusion plus the empty-diff drop
120
+ # erase the write entirely. The concrete case is a classification cascade onto a root
121
+ # already at the requested ``governance_class`` with ``parent_governance_inherited=True``:
122
+ # declaring the class on the root sets the bit to False, which writes the row, bumps
123
+ # ``server_version``, records an undo-ledger row and broadcasts ``tasks_bulk_mutated``
124
+ # — and left the task's Activity tab showing nothing at all.
125
+ #
126
+ # Narrowing the drop rule instead (rendering every empty-diff ``~`` record) was
127
+ # rejected: that is the bare "Updated" pill issue 874 removed, and it would resurrect
128
+ # it for every ``sprint_rank`` reorder and every transient ``sprint_pending`` flip.
129
+ # Promotion keeps the exclusion's intent — no noise beside a change the user can
130
+ # already read — while guaranteeing that no write is invisible.
131
+ HISTORY_DIFF_PROMOTED_WHEN_ALONE: frozenset[str] = frozenset({"parent_governance_inherited"})
132
+
133
+ # Everything a diff never compares, on any surface. Privacy-gated names are excluded
134
+ # here at *compute* time, not at render time, so no pipeline ever holds the value in a
135
+ # structure a later refactor could accidentally serialize.
136
+ HISTORY_DIFF_HARD_EXCLUDED: frozenset[str] = (
137
+ HISTORY_DIFF_META_COLUMNS | HISTORY_DIFF_ENGINE_AND_SYNC | HISTORY_DIFF_PRIVACY_GATED
138
+ )
139
+
140
+ # What the drawer's routine tuple excludes — the name the drawer has always used.
141
+ HISTORY_DIFF_DISPLAY_EXCLUDED: frozenset[str] = (
142
+ HISTORY_DIFF_HARD_EXCLUDED | HISTORY_DIFF_NOISE | HISTORY_DIFF_OBJECT_SCOPED_EXCLUDED
143
+ )
144
+
145
+
146
+ def is_compared(name: str, *, object_scoped: bool) -> bool:
147
+ """Whether a pipeline should diff the field at all.
148
+
149
+ Hard exclusions never are. Noise is compared only if it is promotable, so a
150
+ promoted field can be rendered when it stands alone; the rest of the noise is
151
+ skipped before comparison rather than filtered afterwards, which is what keeps
152
+ the drawer's FK-id collection from resolving ids nobody will see.
153
+ """
154
+ if name in HISTORY_DIFF_HARD_EXCLUDED:
155
+ return False
156
+ if object_scoped and name in HISTORY_DIFF_OBJECT_SCOPED_EXCLUDED:
157
+ return False
158
+ if name in HISTORY_DIFF_NOISE:
159
+ return name in HISTORY_DIFF_PROMOTED_WHEN_ALONE
160
+ return True
161
+
162
+
163
+ def visible_changes(changes: Iterable[T], field_name: Callable[[T], str]) -> list[T]:
164
+ """Apply the promote-when-alone rule to one record's compared changes.
165
+
166
+ Routine changes render as they are. A change in
167
+ :data:`HISTORY_DIFF_PROMOTED_WHEN_ALONE` renders only when the record has no
168
+ routine change to show — it rescues an otherwise invisible write without adding
169
+ a row beside a change the reader can already see. Generic over the change shape
170
+ so the drawer's ``(field, old, new)`` tuples and the generic pipeline's dicts run
171
+ the same rule; ``field_name`` extracts the model field name from either.
172
+ """
173
+ items = list(changes)
174
+ routine = [c for c in items if field_name(c) not in HISTORY_DIFF_PROMOTED_WHEN_ALONE]
175
+ if routine:
176
+ return routine
177
+ return [c for c in items if field_name(c) in HISTORY_DIFF_PROMOTED_WHEN_ALONE]
@@ -0,0 +1,104 @@
1
+ """Serializers for the object change history API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from rest_framework import serializers
8
+
9
+
10
+ class HistoryUserSerializer(serializers.Serializer[Any]):
11
+ """Minimal user representation in a history record."""
12
+
13
+ id = serializers.UUIDField()
14
+ display_name = serializers.SerializerMethodField()
15
+
16
+ def get_display_name(self, obj: Any) -> str:
17
+ full = f"{obj.first_name} {obj.last_name}".strip()
18
+ return full or obj.username
19
+
20
+
21
+ class FieldDiffSerializer(serializers.Serializer[Any]):
22
+ """A single field change within a history record.
23
+
24
+ ``field`` is the model **field name** (``assignee``, ``sprint``), never the
25
+ database column (``assignee_id``) — the same key the per-task history endpoint
26
+ emits, so one client label map serves every history surface.
27
+ """
28
+
29
+ field = serializers.CharField()
30
+ old = serializers.JSONField(allow_null=True)
31
+ new = serializers.JSONField(allow_null=True)
32
+
33
+
34
+ class HistoryRecordSerializer(serializers.Serializer[Any]):
35
+ """One change record: when it happened, what changed, and (for Admins) by whom.
36
+
37
+ ``history_user`` is null for callers below Admin, so a client must treat it as
38
+ optional rather than assume the actor is always present.
39
+ """
40
+
41
+ # This docstring is published as the ``HistoryRecord`` component description in
42
+ # docs/api/openapi.json, so it is integrator-facing copy — keep it about the
43
+ # contract, not the mechanism. The mechanism: the view sets ``hide_user=True``
44
+ # on the serializer context for callers below ``Role.ADMIN`` (redaction, not
45
+ # omission — the key is always present), and computes ``diff`` via a
46
+ # ``prev_record`` comparison injected through context so this serializer needs
47
+ # no ORM access.
48
+
49
+ id = serializers.SerializerMethodField()
50
+ history_date = serializers.DateTimeField()
51
+ history_type = serializers.CharField()
52
+ history_change_reason = serializers.CharField(allow_null=True)
53
+ history_user = serializers.SerializerMethodField()
54
+ diff = serializers.SerializerMethodField()
55
+
56
+ def get_id(self, obj: Any) -> str:
57
+ return str(obj.history_id)
58
+
59
+ def get_history_user(self, obj: Any) -> dict[str, Any] | None:
60
+ if self.context.get("hide_user"):
61
+ return None
62
+ user = obj.history_user
63
+ if user is None:
64
+ return None
65
+ return HistoryUserSerializer(user).data
66
+
67
+ def get_diff(self, obj: Any) -> list[dict[str, Any]]:
68
+ diffs: dict[int, list[dict[str, Any]]] = self.context.get("diffs", {})
69
+ return diffs.get(obj.history_id, [])
70
+
71
+
72
+ class ChangelogEntrySerializer(serializers.Serializer[Any]):
73
+ """One row in the unified project changelog (ADR-0201).
74
+
75
+ The instance is a plain dict from ``changelog.build_project_changelog`` — the
76
+ aggregator already merged, ordered, and diffed across the historical tables,
77
+ so the serializer only renders. ``user`` is exposed only to Owner/Admin
78
+ callers (``hide_user=True`` in context returns null), reusing the same gate as
79
+ :class:`HistoryRecordSerializer`.
80
+ """
81
+
82
+ id = serializers.CharField()
83
+ object_type = serializers.CharField()
84
+ object_id = serializers.CharField()
85
+ object_label = serializers.CharField()
86
+ change_type = serializers.ChoiceField(choices=["created", "updated", "deleted"])
87
+ history_date = serializers.DateTimeField()
88
+ user = serializers.SerializerMethodField()
89
+ changes = FieldDiffSerializer(many=True)
90
+
91
+ def get_user(self, obj: dict[str, Any]) -> dict[str, Any] | None:
92
+ if self.context.get("hide_user"):
93
+ return None
94
+ user = obj.get("history_user")
95
+ if user is None:
96
+ return None
97
+ return HistoryUserSerializer(user).data
98
+
99
+
100
+ class ChangelogResponseSerializer(serializers.Serializer[Any]):
101
+ """Response envelope for the unified project changelog (ADR-0201)."""
102
+
103
+ results = ChangelogEntrySerializer(many=True)
104
+ next_cursor = serializers.CharField(allow_null=True)