trueppm-api 0.4.0__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 (799) hide show
  1. trueppm_api/__init__.py +27 -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 +346 -0
  8. trueppm_api/apps/access/management/commands/migrate_locked.py +96 -0
  9. trueppm_api/apps/access/management/commands/revoke_api_tokens.py +483 -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 +184 -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_squashed_0019_release_0_4.py +302 -0
  24. trueppm_api/apps/access/migrations/0013_userdefinedmentiongroup.py +82 -0
  25. trueppm_api/apps/access/migrations/0014_programmembership_progm_serverver_idx.py +19 -0
  26. trueppm_api/apps/access/migrations/0015_programuserdefinedmentiongroup.py +86 -0
  27. trueppm_api/apps/access/migrations/0016_externalstakeholder.py +67 -0
  28. trueppm_api/apps/access/migrations/0017_viewer_ordinal_one.py +143 -0
  29. trueppm_api/apps/access/migrations/0018_programmembership_sync_seq_and_more.py +52 -0
  30. trueppm_api/apps/access/migrations/0019_programmembership_reinstated_at_and_more.py +22 -0
  31. trueppm_api/apps/access/migrations/__init__.py +0 -0
  32. trueppm_api/apps/access/minter_role.py +38 -0
  33. trueppm_api/apps/access/models.py +588 -0
  34. trueppm_api/apps/access/permissions.py +3263 -0
  35. trueppm_api/apps/access/serializers.py +1007 -0
  36. trueppm_api/apps/access/services.py +878 -0
  37. trueppm_api/apps/access/signals.py +171 -0
  38. trueppm_api/apps/access/tasks.py +50 -0
  39. trueppm_api/apps/access/throttles.py +96 -0
  40. trueppm_api/apps/access/urls.py +191 -0
  41. trueppm_api/apps/access/views.py +1965 -0
  42. trueppm_api/apps/agents/__init__.py +6 -0
  43. trueppm_api/apps/agents/apps.py +11 -0
  44. trueppm_api/apps/agents/canonical.py +73 -0
  45. trueppm_api/apps/agents/deferred.py +108 -0
  46. trueppm_api/apps/agents/management/__init__.py +0 -0
  47. trueppm_api/apps/agents/management/commands/__init__.py +0 -0
  48. trueppm_api/apps/agents/management/commands/audit_prune.py +137 -0
  49. trueppm_api/apps/agents/management/commands/audit_verify.py +101 -0
  50. trueppm_api/apps/agents/middleware.py +44 -0
  51. trueppm_api/apps/agents/migrations/0001_initial.py +231 -0
  52. trueppm_api/apps/agents/migrations/0001_squashed_0003_release_0_4.py +344 -0
  53. trueppm_api/apps/agents/migrations/0002_agentactioncheckpoint.py +71 -0
  54. trueppm_api/apps/agents/migrations/0003_agentactionrefusaldetail.py +58 -0
  55. trueppm_api/apps/agents/migrations/__init__.py +0 -0
  56. trueppm_api/apps/agents/models.py +371 -0
  57. trueppm_api/apps/agents/refusal.py +103 -0
  58. trueppm_api/apps/agents/serializers.py +85 -0
  59. trueppm_api/apps/agents/services.py +397 -0
  60. trueppm_api/apps/agents/signals.py +30 -0
  61. trueppm_api/apps/agents/urls.py +15 -0
  62. trueppm_api/apps/agents/views.py +109 -0
  63. trueppm_api/apps/csvimport/__init__.py +12 -0
  64. trueppm_api/apps/csvimport/apps.py +11 -0
  65. trueppm_api/apps/csvimport/mapping.py +363 -0
  66. trueppm_api/apps/csvimport/migrations/0001_initial.py +74 -0
  67. trueppm_api/apps/csvimport/migrations/0001_squashed_0003_release_0_4.py +90 -0
  68. trueppm_api/apps/csvimport/migrations/0002_schedule_outline_batch_operations.py +37 -0
  69. trueppm_api/apps/csvimport/migrations/0003_csvimportrequest_date_order_and_more.py +26 -0
  70. trueppm_api/apps/csvimport/migrations/__init__.py +0 -0
  71. trueppm_api/apps/csvimport/models.py +111 -0
  72. trueppm_api/apps/csvimport/parser.py +1825 -0
  73. trueppm_api/apps/csvimport/services.py +48 -0
  74. trueppm_api/apps/csvimport/tasks.py +459 -0
  75. trueppm_api/apps/csvimport/template.py +37 -0
  76. trueppm_api/apps/csvimport/urls.py +50 -0
  77. trueppm_api/apps/csvimport/views.py +793 -0
  78. trueppm_api/apps/history/__init__.py +0 -0
  79. trueppm_api/apps/history/apps.py +12 -0
  80. trueppm_api/apps/history/changelog.py +359 -0
  81. trueppm_api/apps/history/diff_policy.py +178 -0
  82. trueppm_api/apps/history/serializers.py +104 -0
  83. trueppm_api/apps/history/signals.py +82 -0
  84. trueppm_api/apps/history/tasks.py +105 -0
  85. trueppm_api/apps/history/urls.py +38 -0
  86. trueppm_api/apps/history/views.py +443 -0
  87. trueppm_api/apps/idempotency/__init__.py +0 -0
  88. trueppm_api/apps/idempotency/apps.py +11 -0
  89. trueppm_api/apps/idempotency/migrations/0001_initial.py +71 -0
  90. trueppm_api/apps/idempotency/migrations/__init__.py +0 -0
  91. trueppm_api/apps/idempotency/mixins.py +226 -0
  92. trueppm_api/apps/idempotency/models.py +75 -0
  93. trueppm_api/apps/idempotency/tasks.py +61 -0
  94. trueppm_api/apps/integrations/__init__.py +13 -0
  95. trueppm_api/apps/integrations/apps.py +59 -0
  96. trueppm_api/apps/integrations/connections.py +806 -0
  97. trueppm_api/apps/integrations/encryption.py +92 -0
  98. trueppm_api/apps/integrations/external_sources.py +863 -0
  99. trueppm_api/apps/integrations/git_automation_services.py +231 -0
  100. trueppm_api/apps/integrations/git_webhook_auth.py +243 -0
  101. trueppm_api/apps/integrations/http.py +597 -0
  102. trueppm_api/apps/integrations/me_work.py +218 -0
  103. trueppm_api/apps/integrations/migrations/0001_initial.py +64 -0
  104. trueppm_api/apps/integrations/migrations/0001_squashed_0006_tasklink_description_tasklink_preview_type_and_more.py +187 -0
  105. trueppm_api/apps/integrations/migrations/0002_tasklink.py +67 -0
  106. trueppm_api/apps/integrations/migrations/0003_tasklink_tasklink_serverver_idx.py +17 -0
  107. trueppm_api/apps/integrations/migrations/0004_tasklink_custom_title_tasklink_labels.py +25 -0
  108. trueppm_api/apps/integrations/migrations/0005_boardautomation.py +56 -0
  109. trueppm_api/apps/integrations/migrations/0006_tasklink_description_tasklink_preview_type_and_more.py +40 -0
  110. trueppm_api/apps/integrations/migrations/0007_squashed_0014_release_0_4.py +225 -0
  111. trueppm_api/apps/integrations/migrations/0007_tasklink_created_at.py +23 -0
  112. trueppm_api/apps/integrations/migrations/0008_integrationcredential_config_externalworkitem.py +66 -0
  113. trueppm_api/apps/integrations/migrations/0009_externalsyncrequest_and_more.py +96 -0
  114. trueppm_api/apps/integrations/migrations/0010_externalworkitem_due_date.py +17 -0
  115. trueppm_api/apps/integrations/migrations/0011_tasklink_sync_seq_tasklink_tasklink_syncseq_idx.py +22 -0
  116. trueppm_api/apps/integrations/migrations/0012_boardautomation_last_delivery_at_and_more.py +42 -0
  117. trueppm_api/apps/integrations/migrations/0013_externalsyncrequest_attempt_count_and_more.py +26 -0
  118. trueppm_api/apps/integrations/migrations/0014_tasklink_created_by.py +27 -0
  119. trueppm_api/apps/integrations/migrations/__init__.py +0 -0
  120. trueppm_api/apps/integrations/models.py +635 -0
  121. trueppm_api/apps/integrations/notification_channels.py +57 -0
  122. trueppm_api/apps/integrations/opengraph.py +229 -0
  123. trueppm_api/apps/integrations/outgoing.py +201 -0
  124. trueppm_api/apps/integrations/providers.py +528 -0
  125. trueppm_api/apps/integrations/registry.py +345 -0
  126. trueppm_api/apps/integrations/serializers.py +453 -0
  127. trueppm_api/apps/integrations/services.py +171 -0
  128. trueppm_api/apps/integrations/tasks.py +734 -0
  129. trueppm_api/apps/integrations/throttles.py +165 -0
  130. trueppm_api/apps/integrations/urls.py +77 -0
  131. trueppm_api/apps/integrations/views.py +1158 -0
  132. trueppm_api/apps/jiraimport/__init__.py +8 -0
  133. trueppm_api/apps/jiraimport/apps.py +11 -0
  134. trueppm_api/apps/jiraimport/migrations/0001_initial.py +72 -0
  135. trueppm_api/apps/jiraimport/migrations/__init__.py +0 -0
  136. trueppm_api/apps/jiraimport/models.py +70 -0
  137. trueppm_api/apps/jiraimport/parser.py +350 -0
  138. trueppm_api/apps/jiraimport/services.py +46 -0
  139. trueppm_api/apps/jiraimport/tasks.py +280 -0
  140. trueppm_api/apps/jiraimport/urls.py +15 -0
  141. trueppm_api/apps/jiraimport/views.py +153 -0
  142. trueppm_api/apps/msproject/__init__.py +0 -0
  143. trueppm_api/apps/msproject/apps.py +11 -0
  144. trueppm_api/apps/msproject/dataclasses.py +153 -0
  145. trueppm_api/apps/msproject/exporter.py +551 -0
  146. trueppm_api/apps/msproject/extended_attributes.py +77 -0
  147. trueppm_api/apps/msproject/importer.py +1095 -0
  148. trueppm_api/apps/msproject/migrations/0001_import_request.py +77 -0
  149. trueppm_api/apps/msproject/migrations/0001_squashed_0002_importrequest_creates_project.py +80 -0
  150. trueppm_api/apps/msproject/migrations/0002_importrequest_creates_project.py +15 -0
  151. trueppm_api/apps/msproject/migrations/__init__.py +0 -0
  152. trueppm_api/apps/msproject/models.py +81 -0
  153. trueppm_api/apps/msproject/parser.py +1276 -0
  154. trueppm_api/apps/msproject/serializers.py +136 -0
  155. trueppm_api/apps/msproject/services.py +58 -0
  156. trueppm_api/apps/msproject/tasks.py +439 -0
  157. trueppm_api/apps/msproject/urls.py +39 -0
  158. trueppm_api/apps/msproject/views.py +531 -0
  159. trueppm_api/apps/notifications/__init__.py +7 -0
  160. trueppm_api/apps/notifications/apps.py +15 -0
  161. trueppm_api/apps/notifications/backfill.py +79 -0
  162. trueppm_api/apps/notifications/categories.py +112 -0
  163. trueppm_api/apps/notifications/delivery_limits.py +258 -0
  164. trueppm_api/apps/notifications/digests.py +462 -0
  165. trueppm_api/apps/notifications/email_backend.py +350 -0
  166. trueppm_api/apps/notifications/email_health.py +128 -0
  167. trueppm_api/apps/notifications/migrations/0001_initial.py +207 -0
  168. trueppm_api/apps/notifications/migrations/0001_squashed_0006_notification_task.py +299 -0
  169. trueppm_api/apps/notifications/migrations/0002_project_notification_preference.py +67 -0
  170. trueppm_api/apps/notifications/migrations/0003_projectnotificationpreference_paused.py +17 -0
  171. trueppm_api/apps/notifications/migrations/0004_clean_unknown_matrix_keys.py +30 -0
  172. trueppm_api/apps/notifications/migrations/0005_notification_body_notification_event_type_and_more.py +27 -0
  173. trueppm_api/apps/notifications/migrations/0006_notification_task.py +25 -0
  174. trueppm_api/apps/notifications/migrations/0007_squashed_0011_release_0_4.py +204 -0
  175. trueppm_api/apps/notifications/migrations/0007_workspaceemailsettings.py +80 -0
  176. trueppm_api/apps/notifications/migrations/0008_notification_snoozed_until.py +17 -0
  177. trueppm_api/apps/notifications/migrations/0009_usernotificationsettings.py +43 -0
  178. trueppm_api/apps/notifications/migrations/0010_projectnotificationpreference_schema_version.py +20 -0
  179. trueppm_api/apps/notifications/migrations/0011_usernotificationsettings_digest_hour_and_more.py +96 -0
  180. trueppm_api/apps/notifications/migrations/__init__.py +0 -0
  181. trueppm_api/apps/notifications/models.py +1148 -0
  182. trueppm_api/apps/notifications/receivers.py +155 -0
  183. trueppm_api/apps/notifications/schema_migrations.py +56 -0
  184. trueppm_api/apps/notifications/serializers.py +1094 -0
  185. trueppm_api/apps/notifications/services.py +1489 -0
  186. trueppm_api/apps/notifications/tasks.py +762 -0
  187. trueppm_api/apps/notifications/throttles.py +98 -0
  188. trueppm_api/apps/notifications/urls.py +97 -0
  189. trueppm_api/apps/notifications/views.py +919 -0
  190. trueppm_api/apps/observability/__init__.py +0 -0
  191. trueppm_api/apps/observability/apps.py +31 -0
  192. trueppm_api/apps/observability/logging.py +213 -0
  193. trueppm_api/apps/observability/migrations/0001_initial.py +35 -0
  194. trueppm_api/apps/observability/migrations/0001_squashed_0003_alter_purgerun_trigger.py +154 -0
  195. trueppm_api/apps/observability/migrations/0002_purgerun_retentionpolicy_retentionschedule.py +131 -0
  196. trueppm_api/apps/observability/migrations/0003_alter_purgerun_trigger.py +21 -0
  197. trueppm_api/apps/observability/migrations/0004_alter_retentionpolicy_key.py +28 -0
  198. trueppm_api/apps/observability/migrations/__init__.py +0 -0
  199. trueppm_api/apps/observability/models.py +141 -0
  200. trueppm_api/apps/observability/otel/__init__.py +45 -0
  201. trueppm_api/apps/observability/otel/attributes.py +247 -0
  202. trueppm_api/apps/observability/otel/export_health.py +533 -0
  203. trueppm_api/apps/observability/otel/instrumentation.py +351 -0
  204. trueppm_api/apps/observability/otel/metrics.py +566 -0
  205. trueppm_api/apps/observability/otel/provider.py +511 -0
  206. trueppm_api/apps/observability/otel/request_attributes.py +204 -0
  207. trueppm_api/apps/observability/purge_registry.py +124 -0
  208. trueppm_api/apps/observability/retention.py +139 -0
  209. trueppm_api/apps/observability/selectors.py +993 -0
  210. trueppm_api/apps/observability/serializers.py +142 -0
  211. trueppm_api/apps/observability/services.py +407 -0
  212. trueppm_api/apps/observability/tasks.py +266 -0
  213. trueppm_api/apps/observability/urls.py +27 -0
  214. trueppm_api/apps/observability/views.py +537 -0
  215. trueppm_api/apps/profiles/__init__.py +0 -0
  216. trueppm_api/apps/profiles/apps.py +15 -0
  217. trueppm_api/apps/profiles/constants.py +54 -0
  218. trueppm_api/apps/profiles/migrations/0001_initial.py +55 -0
  219. trueppm_api/apps/profiles/migrations/0001_squashed_0004_userprofile_role_context.py +125 -0
  220. trueppm_api/apps/profiles/migrations/0002_userprofile_hidden_views.py +17 -0
  221. trueppm_api/apps/profiles/migrations/0003_projectvisit.py +63 -0
  222. trueppm_api/apps/profiles/migrations/0004_userprofile_role_context.py +26 -0
  223. trueppm_api/apps/profiles/migrations/0005_squashed_0009_release_0_4.py +128 -0
  224. trueppm_api/apps/profiles/migrations/0005_userprofile_schedule_in_deliver.py +20 -0
  225. trueppm_api/apps/profiles/migrations/0006_userprofile_date_format_userprofile_timezone.py +36 -0
  226. trueppm_api/apps/profiles/migrations/0007_userpin.py +91 -0
  227. trueppm_api/apps/profiles/migrations/0008_remove_userprofile_schedule_in_deliver.py +71 -0
  228. trueppm_api/apps/profiles/migrations/0009_auth_user_email_upper_idx.py +81 -0
  229. trueppm_api/apps/profiles/migrations/__init__.py +0 -0
  230. trueppm_api/apps/profiles/models.py +276 -0
  231. trueppm_api/apps/profiles/serializers.py +199 -0
  232. trueppm_api/apps/profiles/services.py +541 -0
  233. trueppm_api/apps/profiles/views.py +124 -0
  234. trueppm_api/apps/projects/__init__.py +0 -0
  235. trueppm_api/apps/projects/actual_date_rules.py +163 -0
  236. trueppm_api/apps/projects/amend.py +133 -0
  237. trueppm_api/apps/projects/apps.py +38 -0
  238. trueppm_api/apps/projects/asset_feed.py +459 -0
  239. trueppm_api/apps/projects/asset_views.py +317 -0
  240. trueppm_api/apps/projects/attachment_policy.py +278 -0
  241. trueppm_api/apps/projects/authentication.py +458 -0
  242. trueppm_api/apps/projects/backfill.py +459 -0
  243. trueppm_api/apps/projects/backlog_services.py +324 -0
  244. trueppm_api/apps/projects/backlog_views.py +302 -0
  245. trueppm_api/apps/projects/batch_operation_views.py +264 -0
  246. trueppm_api/apps/projects/blocker_services.py +510 -0
  247. trueppm_api/apps/projects/board_activity.py +462 -0
  248. trueppm_api/apps/projects/board_activity_views.py +186 -0
  249. trueppm_api/apps/projects/board_lanes.py +134 -0
  250. trueppm_api/apps/projects/bulk_settings.py +194 -0
  251. trueppm_api/apps/projects/bundled_templates.py +215 -0
  252. trueppm_api/apps/projects/calendar_settings.py +180 -0
  253. trueppm_api/apps/projects/ceremony_views.py +244 -0
  254. trueppm_api/apps/projects/commit_moment.py +212 -0
  255. trueppm_api/apps/projects/config_notice.py +1330 -0
  256. trueppm_api/apps/projects/custom_field_values.py +266 -0
  257. trueppm_api/apps/projects/decisions_services.py +132 -0
  258. trueppm_api/apps/projects/decisions_views.py +97 -0
  259. trueppm_api/apps/projects/estimation_scale.py +126 -0
  260. trueppm_api/apps/projects/export_bundle.py +423 -0
  261. trueppm_api/apps/projects/fixtures/seeds/atlas-platform-launch.json +4278 -0
  262. trueppm_api/apps/projects/fixtures/seeds/aurora-mobile-app.json +2001 -0
  263. trueppm_api/apps/projects/fixtures/seeds/bayside-civic-center.json +1827 -0
  264. trueppm_api/apps/projects/fixtures/seeds/ga-launch.json +1909 -0
  265. trueppm_api/apps/projects/fixtures/seeds/helios-crm-replacement.json +1410 -0
  266. trueppm_api/apps/projects/guardrail_policy_source.py +269 -0
  267. trueppm_api/apps/projects/inbound_sync.py +555 -0
  268. trueppm_api/apps/projects/iteration_label.py +85 -0
  269. trueppm_api/apps/projects/key_views.py +468 -0
  270. trueppm_api/apps/projects/keys.py +205 -0
  271. trueppm_api/apps/projects/lifecycle.py +191 -0
  272. trueppm_api/apps/projects/management/__init__.py +0 -0
  273. trueppm_api/apps/projects/management/commands/__init__.py +0 -0
  274. trueppm_api/apps/projects/management/commands/backfill_in_progress_status.py +92 -0
  275. trueppm_api/apps/projects/management/commands/create_demo_share_link.py +368 -0
  276. trueppm_api/apps/projects/management/commands/export_program.py +50 -0
  277. trueppm_api/apps/projects/management/commands/import_seed.py +165 -0
  278. trueppm_api/apps/projects/management/commands/load_sample_project.py +514 -0
  279. trueppm_api/apps/projects/management/commands/seed_capacity.py +406 -0
  280. trueppm_api/apps/projects/management/commands/seed_integration_fixtures.py +192 -0
  281. trueppm_api/apps/projects/management/commands/shift_sample_dates.py +120 -0
  282. trueppm_api/apps/projects/mcp_settings.py +305 -0
  283. trueppm_api/apps/projects/methodology.py +178 -0
  284. trueppm_api/apps/projects/migration_helpers.py +75 -0
  285. trueppm_api/apps/projects/migrations/0001_initial.py +208 -0
  286. trueppm_api/apps/projects/migrations/0001_squashed_0094_project_decisions_policy.py +5659 -0
  287. trueppm_api/apps/projects/migrations/0002_alter_uuid_pk_serialize_false.py +57 -0
  288. trueppm_api/apps/projects/migrations/0003_rename_task_project_index.py +25 -0
  289. trueppm_api/apps/projects/migrations/0004_soft_delete_and_dependency_versioned.py +70 -0
  290. trueppm_api/apps/projects/migrations/0005_task_assignee.py +33 -0
  291. trueppm_api/apps/projects/migrations/0006_task_utilization_index.py +31 -0
  292. trueppm_api/apps/projects/migrations/0007_task_is_milestone.py +28 -0
  293. trueppm_api/apps/projects/migrations/0008_baseline.py +119 -0
  294. trueppm_api/apps/projects/migrations/0009_historicaldependency_historicalproject_and_more.py +210 -0
  295. trueppm_api/apps/projects/migrations/0010_risk.py +229 -0
  296. trueppm_api/apps/projects/migrations/0011_task_status.py +27 -0
  297. trueppm_api/apps/projects/migrations/0012_task_status.py +27 -0
  298. trueppm_api/apps/projects/migrations/0013_task_planned_start.py +24 -0
  299. trueppm_api/apps/projects/migrations/0014_short_hex_ids.py +74 -0
  300. trueppm_api/apps/projects/migrations/0015_backfill_short_ids.py +48 -0
  301. trueppm_api/apps/projects/migrations/0016_task_actual_dates.py +42 -0
  302. trueppm_api/apps/projects/migrations/0017_boardcolumnconfig.py +43 -0
  303. trueppm_api/apps/projects/migrations/0018_estimation_governance.py +62 -0
  304. trueppm_api/apps/projects/migrations/0019_backfill_wbs_paths.py +28 -0
  305. trueppm_api/apps/projects/migrations/0020_task_status_5col.py +65 -0
  306. trueppm_api/apps/projects/migrations/0021_boardsavedview.py +64 -0
  307. trueppm_api/apps/projects/migrations/0022_task_status_changed_at_priority_rank.py +35 -0
  308. trueppm_api/apps/projects/migrations/0023_risk_framework_fields.py +96 -0
  309. trueppm_api/apps/projects/migrations/0024_riskcomment.py +52 -0
  310. trueppm_api/apps/projects/migrations/0025_sprint.py +352 -0
  311. trueppm_api/apps/projects/migrations/0026_project_methodology.py +30 -0
  312. trueppm_api/apps/projects/migrations/0027_sprint_retro.py +89 -0
  313. trueppm_api/apps/projects/migrations/0028_normalize_notes_field.py +42 -0
  314. trueppm_api/apps/projects/migrations/0029_complete_implies_full_progress.py +38 -0
  315. trueppm_api/apps/projects/migrations/0030_review_implies_full_progress.py +37 -0
  316. trueppm_api/apps/projects/migrations/0031_task_remaining_points.py +22 -0
  317. trueppm_api/apps/projects/migrations/0032_task_is_subtask_and_sprint_scope_change.py +90 -0
  318. trueppm_api/apps/projects/migrations/0033_task_my_work_index.py +38 -0
  319. trueppm_api/apps/projects/migrations/0034_inbound_task_sync.py +313 -0
  320. trueppm_api/apps/projects/migrations/0035_retro_versioned_and_suggested_assignee.py +173 -0
  321. trueppm_api/apps/projects/migrations/0036_program_entity_and_membership.py +157 -0
  322. trueppm_api/apps/projects/migrations/0037_sprint_capacity_points.py +20 -0
  323. trueppm_api/apps/projects/migrations/0038_taskcomment_commentreaction_commentacknowledgement_and_more.py +233 -0
  324. trueppm_api/apps/projects/migrations/0039_apitoken_and_more.py +88 -0
  325. trueppm_api/apps/projects/migrations/0040_program_general_fields.py +119 -0
  326. trueppm_api/apps/projects/migrations/0041_ceremony_template_and_phase_gate_config.py +210 -0
  327. trueppm_api/apps/projects/migrations/0041_project_general_fields.py +106 -0
  328. trueppm_api/apps/projects/migrations/0042_merge_20260522_0410.py +12 -0
  329. trueppm_api/apps/projects/migrations/0042_program_rollup_config.py +95 -0
  330. trueppm_api/apps/projects/migrations/0042_workflow_color_and_custom_fields.py +94 -0
  331. trueppm_api/apps/projects/migrations/0043_merge_20260522_1118.py +10 -0
  332. trueppm_api/apps/projects/migrations/0044_merge_20260522_rollup.py +10 -0
  333. trueppm_api/apps/projects/migrations/0044_project_archive_program_close.py +119 -0
  334. trueppm_api/apps/projects/migrations/0045_program_risk_policy.py +78 -0
  335. trueppm_api/apps/projects/migrations/0046_merge_20260523_2248.py +12 -0
  336. trueppm_api/apps/projects/migrations/0047_apitokenauditentry_program_and_more.py +55 -0
  337. trueppm_api/apps/projects/migrations/0048_historicalprogram_color_program_color.py +32 -0
  338. trueppm_api/apps/projects/migrations/0049_backlog_item.py +112 -0
  339. trueppm_api/apps/projects/migrations/0050_backlog_item_trgm_search.py +26 -0
  340. trueppm_api/apps/projects/migrations/0051_alter_calendar_working_days_alter_dependency_lag_and_more.py +144 -0
  341. trueppm_api/apps/projects/migrations/0052_recurring_tasks.py +278 -0
  342. trueppm_api/apps/projects/migrations/0053_dependency_dep_pred_serverver_idx_and_more.py +56 -0
  343. trueppm_api/apps/projects/migrations/0054_sprintscopechange_goal_impact_and_more.py +117 -0
  344. trueppm_api/apps/projects/migrations/0055_historicaltask_sprint_pending_and_more.py +51 -0
  345. trueppm_api/apps/projects/migrations/0056_historicalproject_prioritization_model_and_more.py +276 -0
  346. trueppm_api/apps/projects/migrations/0057_historicalsprint_binding_committed_snapshot_and_more.py +58 -0
  347. trueppm_api/apps/projects/migrations/0058_historicaltask_delivery_mode_and_more.py +72 -0
  348. trueppm_api/apps/projects/migrations/0059_forecastsnapshot.py +80 -0
  349. trueppm_api/apps/projects/migrations/0060_historicalprojectsignalprivacypolicy_and_more.py +90 -0
  350. trueppm_api/apps/projects/migrations/0061_baselinetask_story_points.py +17 -0
  351. trueppm_api/apps/projects/migrations/0062_project_is_sample.py +22 -0
  352. trueppm_api/apps/projects/migrations/0063_historicalsprint_wip_limit_sprint_wip_limit.py +22 -0
  353. trueppm_api/apps/projects/migrations/0064_sprinttaskoutcome.py +97 -0
  354. trueppm_api/apps/projects/migrations/0065_historicalsprint_goal_outcome_sprint_goal_outcome.py +32 -0
  355. trueppm_api/apps/projects/migrations/0066_historicalproject_lead_project_lead.py +38 -0
  356. trueppm_api/apps/projects/migrations/0067_sprint_exclude_from_velocity.py +22 -0
  357. trueppm_api/apps/projects/migrations/0068_project_recalculated_at.py +23 -0
  358. trueppm_api/apps/projects/migrations/0069_iteration_label.py +22 -0
  359. trueppm_api/apps/projects/migrations/0070_historicalprogram_iteration_label_and_more.py +32 -0
  360. trueppm_api/apps/projects/migrations/0071_backfill_iteration_label_inherit.py +35 -0
  361. trueppm_api/apps/projects/migrations/0072_pulseresponse_retroboarditem.py +129 -0
  362. trueppm_api/apps/projects/migrations/0073_sprinttaskoutcome_demo_ready.py +17 -0
  363. trueppm_api/apps/projects/migrations/0074_historicaltask_blocked_reason_task_blocked_reason.py +30 -0
  364. trueppm_api/apps/projects/migrations/0075_risk_decimal_short_id.py +42 -0
  365. trueppm_api/apps/projects/migrations/0076_sprinttaskoutcome_review_polish.py +45 -0
  366. trueppm_api/apps/projects/migrations/0077_task_blocker_fields.py +111 -0
  367. trueppm_api/apps/projects/migrations/0078_alter_risktask_unique_together_and_more.py +22 -0
  368. trueppm_api/apps/projects/migrations/0079_historicalproject_status_date_project_status_date.py +22 -0
  369. trueppm_api/apps/projects/migrations/0080_alter_historicaltask_type_alter_task_type.py +46 -0
  370. trueppm_api/apps/projects/migrations/0081_historicalprogram_allow_guests_and_more.py +52 -0
  371. trueppm_api/apps/projects/migrations/0082_project_last_sync_version.py +90 -0
  372. trueppm_api/apps/projects/migrations/0083_historicalprogram_mc_history_attribution_audience_and_more.py +108 -0
  373. trueppm_api/apps/projects/migrations/0084_tasknote.py +71 -0
  374. trueppm_api/apps/projects/migrations/0085_sprint_ix_sprint_velocity.py +33 -0
  375. trueppm_api/apps/projects/migrations/0086_board_card_search_trgm.py +33 -0
  376. trueppm_api/apps/projects/migrations/0087_historicalprogram_task_duration_change_percent_policy_and_more.py +155 -0
  377. trueppm_api/apps/projects/migrations/0088_historicalprogram_allowed_attachment_types_and_more.py +61 -0
  378. trueppm_api/apps/projects/migrations/0089_signalceilingraiseproposal_signalceilingraisevote_and_more.py +157 -0
  379. trueppm_api/apps/projects/migrations/0090_historicaltask_proj_histdate_index.py +59 -0
  380. trueppm_api/apps/projects/migrations/0091_project_board_cadence.py +39 -0
  381. trueppm_api/apps/projects/migrations/0092_program_target_date.py +22 -0
  382. trueppm_api/apps/projects/migrations/0093_cross_project_dependency_consent.py +58 -0
  383. trueppm_api/apps/projects/migrations/0094_project_decisions_policy.py +90 -0
  384. trueppm_api/apps/projects/migrations/0095_poker_session_vote.py +127 -0
  385. trueppm_api/apps/projects/migrations/0095_squashed_0097_release_0_3.py +225 -0
  386. trueppm_api/apps/projects/migrations/0096_crossprojectslipconflict.py +91 -0
  387. trueppm_api/apps/projects/migrations/0097_backlogitem_backlogitem_tags_gin_and_more.py +32 -0
  388. trueppm_api/apps/projects/migrations/0098_historicalproject_show_baselines_and_more.py +52 -0
  389. trueppm_api/apps/projects/migrations/0099_apitoken_scopes.py +48 -0
  390. trueppm_api/apps/projects/migrations/0100_task_dep_serverver_idx_concurrent.py +75 -0
  391. trueppm_api/apps/projects/migrations/0101_dependency_deleted_at_task_deleted_at.py +22 -0
  392. trueppm_api/apps/projects/migrations/0102_project_deleted_at_project_proj_isdel_deletedat_idx.py +25 -0
  393. trueppm_api/apps/projects/migrations/0103_taskdurationchangeevent_task_dur_evt_sprint_idx.py +18 -0
  394. trueppm_api/apps/projects/migrations/0104_historicalproject_stale_task_threshold_days_and_more.py +32 -0
  395. trueppm_api/apps/projects/migrations/0104_trash_restore_1113.py +38 -0
  396. trueppm_api/apps/projects/migrations/0105_historical_changelog_indexes.py +76 -0
  397. trueppm_api/apps/projects/migrations/0106_boardsavedview_schema_version.py +20 -0
  398. trueppm_api/apps/projects/migrations/0106_squashed_0158_release_0_4.py +2357 -0
  399. trueppm_api/apps/projects/migrations/0107_task_activity_event.py +70 -0
  400. trueppm_api/apps/projects/migrations/0108_remove_apitoken_api_token_scope_xor_and_more.py +117 -0
  401. trueppm_api/apps/projects/migrations/0109_projectexportjob.py +82 -0
  402. trueppm_api/apps/projects/migrations/0110_sharelink.py +123 -0
  403. trueppm_api/apps/projects/migrations/0111_projectcalendarlayer.py +67 -0
  404. trueppm_api/apps/projects/migrations/0112_sharelink_expires_at_alter_sharelink_content_kind.py +31 -0
  405. trueppm_api/apps/projects/migrations/0113_alter_historicaltask_percent_complete_and_more.py +35 -0
  406. trueppm_api/apps/projects/migrations/0114_historicalproject_default_member_role_and_more.py +28 -0
  407. trueppm_api/apps/projects/migrations/0115_historicalproject_end_date_shift_threshold_days_and_more.py +22 -0
  408. trueppm_api/apps/projects/migrations/0116_alter_taskactivityevent_event_type.py +28 -0
  409. trueppm_api/apps/projects/migrations/0117_label_tasklabel_label_tasks_and_more.py +126 -0
  410. trueppm_api/apps/projects/migrations/0118_programexportjob.py +82 -0
  411. trueppm_api/apps/projects/migrations/0119_historicalprogram_calendar_program_calendar.py +36 -0
  412. trueppm_api/apps/projects/migrations/0120_alter_backlogitem_item_type.py +29 -0
  413. trueppm_api/apps/projects/migrations/0121_task_three_point_estimate_ordered.py +64 -0
  414. trueppm_api/apps/projects/migrations/0122_task_relation.py +173 -0
  415. trueppm_api/apps/projects/migrations/0123_remove_historicalproject_agile_features_and_more.py +20 -0
  416. trueppm_api/apps/projects/migrations/0124_dependency_is_driving.py +17 -0
  417. trueppm_api/apps/projects/migrations/0125_historicalprogram_estimation_scale_and_more.py +68 -0
  418. trueppm_api/apps/projects/migrations/0126_projectcustomfield_show_on_card_taskcustomfieldvalue.py +80 -0
  419. trueppm_api/apps/projects/migrations/0127_historicalprogram_mcp_enabled_and_more.py +32 -0
  420. trueppm_api/apps/projects/migrations/0128_acceptancecriterion_sync_seq_apitoken_sync_seq_and_more.py +192 -0
  421. trueppm_api/apps/projects/migrations/0129_sharelink_show_milestone_dates.py +20 -0
  422. trueppm_api/apps/projects/migrations/0130_programimportjob.py +85 -0
  423. trueppm_api/apps/projects/migrations/0131_historicalprogram_sprint_picker_ready_only_default_and_more.py +32 -0
  424. trueppm_api/apps/projects/migrations/0132_task_scheduled_start.py +17 -0
  425. trueppm_api/apps/projects/migrations/0133_project_status_date_floor_armed_at.py +17 -0
  426. trueppm_api/apps/projects/migrations/0134_task_edited_at_task_seeded_at_task_source_id_and_more.py +65 -0
  427. trueppm_api/apps/projects/migrations/0135_projecttemplate_templateapplication_and_more.py +175 -0
  428. trueppm_api/apps/projects/migrations/0136_schedule_outline_batch_operations.py +118 -0
  429. trueppm_api/apps/projects/migrations/0137_collapse_dual_scope_api_tokens.py +54 -0
  430. trueppm_api/apps/projects/migrations/0138_apitokenauditentry_api_token_audit_owner_idx.py +18 -0
  431. trueppm_api/apps/projects/migrations/0139_historicaltask_duration_unit_task_duration_unit.py +32 -0
  432. trueppm_api/apps/projects/migrations/0140_historicaltask_auto_container_and_more.py +113 -0
  433. trueppm_api/apps/projects/migrations/0141_historicalproject_draft_started_at_and_more.py +44 -0
  434. trueppm_api/apps/projects/migrations/0142_baseline_calendar_hours_per_day_and_more.py +31 -0
  435. trueppm_api/apps/projects/migrations/0143_sprintcloserequest_failure_reason_and_more.py +42 -0
  436. trueppm_api/apps/projects/migrations/0144_projecttemplate_source_project_and_more.py +35 -0
  437. trueppm_api/apps/projects/migrations/0145_seed_bundled_templates.py +65 -0
  438. trueppm_api/apps/projects/migrations/0146_historicaltask_board_lane_task_board_lane.py +32 -0
  439. trueppm_api/apps/projects/migrations/0147_structuraloperation.py +128 -0
  440. trueppm_api/apps/projects/migrations/0148_task_unique_task_wbs_path_per_project_live.py +76 -0
  441. trueppm_api/apps/projects/migrations/0149_drop_workshops_tables.py +42 -0
  442. trueppm_api/apps/projects/migrations/0150_clear_uncommitted_cpm_output.py +130 -0
  443. trueppm_api/apps/projects/migrations/0151_config_notice_request.py +70 -0
  444. trueppm_api/apps/projects/migrations/0152_aggregate_task_wbs_name_idx.py +20 -0
  445. trueppm_api/apps/projects/migrations/0153_program_sample_anchor_date.py +22 -0
  446. trueppm_api/apps/projects/migrations/0154_object_keys.py +184 -0
  447. trueppm_api/apps/projects/migrations/0155_task_milestone_at_day_end.py +20 -0
  448. trueppm_api/apps/projects/migrations/0156_program_import_job_replaced_project_ids.py +17 -0
  449. trueppm_api/apps/projects/migrations/0157_forecastsnapshot_cpm_finish_at_day_start.py +17 -0
  450. trueppm_api/apps/projects/migrations/0158_baselinetask_finish_at_day_start.py +17 -0
  451. trueppm_api/apps/projects/migrations/__init__.py +0 -0
  452. trueppm_api/apps/projects/models.py +9209 -0
  453. trueppm_api/apps/projects/poker_services.py +154 -0
  454. trueppm_api/apps/projects/poker_views.py +398 -0
  455. trueppm_api/apps/projects/product_backlog_services.py +450 -0
  456. trueppm_api/apps/projects/program_label_services.py +94 -0
  457. trueppm_api/apps/projects/program_label_views.py +189 -0
  458. trueppm_api/apps/projects/program_rollup.py +903 -0
  459. trueppm_api/apps/projects/program_schedule.py +767 -0
  460. trueppm_api/apps/projects/program_views.py +3445 -0
  461. trueppm_api/apps/projects/project_templates.py +422 -0
  462. trueppm_api/apps/projects/receivers.py +234 -0
  463. trueppm_api/apps/projects/refusal_codes.py +140 -0
  464. trueppm_api/apps/projects/reorder_services.py +145 -0
  465. trueppm_api/apps/projects/restructure_hooks.py +54 -0
  466. trueppm_api/apps/projects/retro_board_services.py +368 -0
  467. trueppm_api/apps/projects/retro_services.py +285 -0
  468. trueppm_api/apps/projects/risk_import.py +396 -0
  469. trueppm_api/apps/projects/row_identity.py +113 -0
  470. trueppm_api/apps/projects/schema_migrations.py +228 -0
  471. trueppm_api/apps/projects/schemas/seed_v1.json +492 -0
  472. trueppm_api/apps/projects/schemas/seed_v2.json +891 -0
  473. trueppm_api/apps/projects/seed/__init__.py +61 -0
  474. trueppm_api/apps/projects/seed/demo_landing_overlay.py +532 -0
  475. trueppm_api/apps/projects/seed/exporter.py +1717 -0
  476. trueppm_api/apps/projects/seed/forecast_backfill.py +247 -0
  477. trueppm_api/apps/projects/seed/importer.py +2764 -0
  478. trueppm_api/apps/projects/seed/reanchor.py +551 -0
  479. trueppm_api/apps/projects/seed/reldates.py +126 -0
  480. trueppm_api/apps/projects/seed/replace.py +128 -0
  481. trueppm_api/apps/projects/seed/replay.py +1695 -0
  482. trueppm_api/apps/projects/seed/replay_ctx.py +41 -0
  483. trueppm_api/apps/projects/seed/samples.py +427 -0
  484. trueppm_api/apps/projects/seed/validation.py +1169 -0
  485. trueppm_api/apps/projects/serializers.py +12904 -0
  486. trueppm_api/apps/projects/services.py +7525 -0
  487. trueppm_api/apps/projects/share_serializers.py +142 -0
  488. trueppm_api/apps/projects/share_services.py +373 -0
  489. trueppm_api/apps/projects/share_views.py +395 -0
  490. trueppm_api/apps/projects/sharing_settings.py +187 -0
  491. trueppm_api/apps/projects/signal_privacy_services.py +910 -0
  492. trueppm_api/apps/projects/signal_privacy_views.py +485 -0
  493. trueppm_api/apps/projects/signals.py +132 -0
  494. trueppm_api/apps/projects/slip_conflict.py +288 -0
  495. trueppm_api/apps/projects/sprint_cadence.py +453 -0
  496. trueppm_api/apps/projects/sprint_picker_settings.py +132 -0
  497. trueppm_api/apps/projects/standup.py +304 -0
  498. trueppm_api/apps/projects/standup_views.py +99 -0
  499. trueppm_api/apps/projects/structural_operation_services.py +727 -0
  500. trueppm_api/apps/projects/structural_operation_views.py +311 -0
  501. trueppm_api/apps/projects/surface_visibility.py +126 -0
  502. trueppm_api/apps/projects/task_batch_services.py +379 -0
  503. trueppm_api/apps/projects/task_bulk.py +1244 -0
  504. trueppm_api/apps/projects/task_classification.py +561 -0
  505. trueppm_api/apps/projects/task_classification_defaults.py +95 -0
  506. trueppm_api/apps/projects/task_duration_settings.py +142 -0
  507. trueppm_api/apps/projects/task_grouping.py +754 -0
  508. trueppm_api/apps/projects/tasks.py +1979 -0
  509. trueppm_api/apps/projects/template_divergence.py +168 -0
  510. trueppm_api/apps/projects/template_services.py +160 -0
  511. trueppm_api/apps/projects/template_tasks.py +261 -0
  512. trueppm_api/apps/projects/template_views.py +829 -0
  513. trueppm_api/apps/projects/throttles.py +311 -0
  514. trueppm_api/apps/projects/urls.py +1016 -0
  515. trueppm_api/apps/projects/utilization.py +909 -0
  516. trueppm_api/apps/projects/views.py +21225 -0
  517. trueppm_api/apps/projects/wbs_paths.py +119 -0
  518. trueppm_api/apps/resources/__init__.py +0 -0
  519. trueppm_api/apps/resources/apps.py +8 -0
  520. trueppm_api/apps/resources/capacity.py +121 -0
  521. trueppm_api/apps/resources/migrations/0001_initial.py +68 -0
  522. trueppm_api/apps/resources/migrations/0001_squashed_0013_alter_projectresource_unique_together_and_more.py +281 -0
  523. trueppm_api/apps/resources/migrations/0002_alter_uuid_pk_serialize_false.py +36 -0
  524. trueppm_api/apps/resources/migrations/0003_resource_soft_delete.py +30 -0
  525. trueppm_api/apps/resources/migrations/0004_add_taskresource_resource_index.py +25 -0
  526. trueppm_api/apps/resources/migrations/0005_resource_job_role.py +20 -0
  527. trueppm_api/apps/resources/migrations/0006_skill.py +34 -0
  528. trueppm_api/apps/resources/migrations/0007_resource_skill.py +63 -0
  529. trueppm_api/apps/resources/migrations/0008_project_resource.py +63 -0
  530. trueppm_api/apps/resources/migrations/0009_task_skill_requirement.py +60 -0
  531. trueppm_api/apps/resources/migrations/0010_alter_resourceskill_options_and_more.py +20 -0
  532. trueppm_api/apps/resources/migrations/0011_alter_projectresource_notes.py +17 -0
  533. trueppm_api/apps/resources/migrations/0012_add_resource_user_fk.py +26 -0
  534. trueppm_api/apps/resources/migrations/0013_alter_projectresource_unique_together_and_more.py +53 -0
  535. trueppm_api/apps/resources/migrations/0014_projectresource_sync_seq_resource_sync_seq_and_more.py +37 -0
  536. trueppm_api/apps/resources/migrations/0014_squashed_0015_release_0_4.py +47 -0
  537. trueppm_api/apps/resources/migrations/0015_projectresource_deactivated_with_resource.py +17 -0
  538. trueppm_api/apps/resources/migrations/__init__.py +0 -0
  539. trueppm_api/apps/resources/models.py +298 -0
  540. trueppm_api/apps/resources/serializers.py +426 -0
  541. trueppm_api/apps/resources/services.py +465 -0
  542. trueppm_api/apps/resources/urls.py +28 -0
  543. trueppm_api/apps/resources/views.py +1752 -0
  544. trueppm_api/apps/scheduling/__init__.py +0 -0
  545. trueppm_api/apps/scheduling/apps.py +153 -0
  546. trueppm_api/apps/scheduling/calendars.py +144 -0
  547. trueppm_api/apps/scheduling/deadletter.py +91 -0
  548. trueppm_api/apps/scheduling/finish_reading.py +198 -0
  549. trueppm_api/apps/scheduling/forecast_history_settings.py +215 -0
  550. trueppm_api/apps/scheduling/forecast_staleness.py +313 -0
  551. trueppm_api/apps/scheduling/graph_guard.py +263 -0
  552. trueppm_api/apps/scheduling/management/__init__.py +0 -0
  553. trueppm_api/apps/scheduling/management/commands/__init__.py +0 -0
  554. trueppm_api/apps/scheduling/management/commands/prune_forecast_snapshots.py +53 -0
  555. trueppm_api/apps/scheduling/mc_admission.py +181 -0
  556. trueppm_api/apps/scheduling/migrations/0001_failed_task.py +62 -0
  557. trueppm_api/apps/scheduling/migrations/0001_squashed_0007_projectforecastsnapshot.py +309 -0
  558. trueppm_api/apps/scheduling/migrations/0002_schedulerequest.py +78 -0
  559. trueppm_api/apps/scheduling/migrations/0003_add_schedule_request_reason.py +27 -0
  560. trueppm_api/apps/scheduling/migrations/0004_velocity_suggestion.py +94 -0
  561. trueppm_api/apps/scheduling/migrations/0005_montecarlorun.py +61 -0
  562. trueppm_api/apps/scheduling/migrations/0006_montecarlorun_distribution.py +17 -0
  563. trueppm_api/apps/scheduling/migrations/0007_projectforecastsnapshot.py +65 -0
  564. trueppm_api/apps/scheduling/migrations/0008_alter_schedulerequest_reason.py +28 -0
  565. trueppm_api/apps/scheduling/migrations/0008_folded_schedulerequest_reason.py +34 -0
  566. trueppm_api/apps/scheduling/migrations/0009_alter_schedulerequest_reason.py +29 -0
  567. trueppm_api/apps/scheduling/migrations/0010_failedtask_resolution_note_failedtask_resolved_at_and_more.py +43 -0
  568. trueppm_api/apps/scheduling/migrations/0010_squashed_0016_release_0_4.py +106 -0
  569. trueppm_api/apps/scheduling/migrations/0011_failedtask_project.py +26 -0
  570. trueppm_api/apps/scheduling/migrations/0012_montecarlorun_diagnostic.py +17 -0
  571. trueppm_api/apps/scheduling/migrations/0013_montecarlorun_status_date.py +17 -0
  572. trueppm_api/apps/scheduling/migrations/0014_montecarlorun_plan_version.py +17 -0
  573. trueppm_api/apps/scheduling/migrations/0015_projectforecastsnapshot_cpm_finish_at_day_start.py +17 -0
  574. trueppm_api/apps/scheduling/migrations/0016_montecarlorun_finish_readings.py +32 -0
  575. trueppm_api/apps/scheduling/migrations/__init__.py +0 -0
  576. trueppm_api/apps/scheduling/models.py +542 -0
  577. trueppm_api/apps/scheduling/receivers.py +160 -0
  578. trueppm_api/apps/scheduling/risk_premium.py +334 -0
  579. trueppm_api/apps/scheduling/serializers.py +723 -0
  580. trueppm_api/apps/scheduling/services.py +1251 -0
  581. trueppm_api/apps/scheduling/signals.py +56 -0
  582. trueppm_api/apps/scheduling/tasks.py +2248 -0
  583. trueppm_api/apps/scheduling/telemetry.py +80 -0
  584. trueppm_api/apps/scheduling/units.py +35 -0
  585. trueppm_api/apps/scheduling/urls.py +57 -0
  586. trueppm_api/apps/scheduling/views.py +2908 -0
  587. trueppm_api/apps/sso/__init__.py +0 -0
  588. trueppm_api/apps/sso/apps.py +19 -0
  589. trueppm_api/apps/sso/extensions.py +130 -0
  590. trueppm_api/apps/sso/management/__init__.py +0 -0
  591. trueppm_api/apps/sso/management/commands/__init__.py +0 -0
  592. trueppm_api/apps/sso/management/commands/seed_sso_keycloak.py +172 -0
  593. trueppm_api/apps/sso/migrations/0001_initial.py +119 -0
  594. trueppm_api/apps/sso/migrations/0002_remove_oidcprovider_workspace_ssoproviderpolicy_and_more.py +169 -0
  595. trueppm_api/apps/sso/migrations/__init__.py +0 -0
  596. trueppm_api/apps/sso/models.py +148 -0
  597. trueppm_api/apps/sso/serializers.py +446 -0
  598. trueppm_api/apps/sso/services.py +1473 -0
  599. trueppm_api/apps/sso/urls.py +47 -0
  600. trueppm_api/apps/sso/views.py +935 -0
  601. trueppm_api/apps/sync/__init__.py +0 -0
  602. trueppm_api/apps/sync/apps.py +15 -0
  603. trueppm_api/apps/sync/backfill.py +208 -0
  604. trueppm_api/apps/sync/broadcast.py +369 -0
  605. trueppm_api/apps/sync/conflict.py +262 -0
  606. trueppm_api/apps/sync/consumers.py +548 -0
  607. trueppm_api/apps/sync/migrations/0001_initial.py +59 -0
  608. trueppm_api/apps/sync/migrations/0001_squashed_0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +77 -0
  609. trueppm_api/apps/sync/migrations/0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +41 -0
  610. trueppm_api/apps/sync/migrations/0003_boardevent.py +46 -0
  611. trueppm_api/apps/sync/migrations/0003_squashed_0005_release_0_4.py +77 -0
  612. trueppm_api/apps/sync/migrations/0004_seed_sync_seq.py +24 -0
  613. trueppm_api/apps/sync/migrations/0005_program_sync_sequence.py +35 -0
  614. trueppm_api/apps/sync/migrations/0006_boardevent_per_project_seq.py +77 -0
  615. trueppm_api/apps/sync/migrations/__init__.py +0 -0
  616. trueppm_api/apps/sync/models.py +272 -0
  617. trueppm_api/apps/sync/pagination.py +231 -0
  618. trueppm_api/apps/sync/sequence.py +488 -0
  619. trueppm_api/apps/sync/serializers.py +688 -0
  620. trueppm_api/apps/sync/tasks.py +260 -0
  621. trueppm_api/apps/sync/throttles.py +175 -0
  622. trueppm_api/apps/sync/upload.py +556 -0
  623. trueppm_api/apps/sync/urls.py +17 -0
  624. trueppm_api/apps/sync/views.py +957 -0
  625. trueppm_api/apps/sync/ws_auth.py +193 -0
  626. trueppm_api/apps/taskruns/__init__.py +0 -0
  627. trueppm_api/apps/taskruns/apps.py +11 -0
  628. trueppm_api/apps/taskruns/migrations/0001_initial.py +92 -0
  629. trueppm_api/apps/taskruns/migrations/__init__.py +0 -0
  630. trueppm_api/apps/taskruns/models.py +68 -0
  631. trueppm_api/apps/taskruns/serializers.py +92 -0
  632. trueppm_api/apps/taskruns/tasks.py +60 -0
  633. trueppm_api/apps/taskruns/tracker.py +286 -0
  634. trueppm_api/apps/taskruns/urls.py +46 -0
  635. trueppm_api/apps/taskruns/views.py +195 -0
  636. trueppm_api/apps/teams/__init__.py +8 -0
  637. trueppm_api/apps/teams/apps.py +16 -0
  638. trueppm_api/apps/teams/migrations/0001_initial.py +139 -0
  639. trueppm_api/apps/teams/migrations/0001_squashed_0002_default_teams.py +159 -0
  640. trueppm_api/apps/teams/migrations/0002_default_teams.py +62 -0
  641. trueppm_api/apps/teams/migrations/0003_team_sync_seq_teammembership_sync_seq.py +22 -0
  642. trueppm_api/apps/teams/migrations/0004_backfill_default_teams_on_upgrade.py +49 -0
  643. trueppm_api/apps/teams/migrations/__init__.py +0 -0
  644. trueppm_api/apps/teams/models.py +145 -0
  645. trueppm_api/apps/teams/permissions.py +149 -0
  646. trueppm_api/apps/teams/serializers.py +95 -0
  647. trueppm_api/apps/teams/services.py +440 -0
  648. trueppm_api/apps/teams/signals.py +61 -0
  649. trueppm_api/apps/teams/urls.py +34 -0
  650. trueppm_api/apps/teams/views.py +201 -0
  651. trueppm_api/apps/timetracking/__init__.py +0 -0
  652. trueppm_api/apps/timetracking/apps.py +9 -0
  653. trueppm_api/apps/timetracking/migrations/0001_initial.py +117 -0
  654. trueppm_api/apps/timetracking/migrations/0001_squashed_0004_release_0_4.py +167 -0
  655. trueppm_api/apps/timetracking/migrations/0002_timesheetsubmission.py +46 -0
  656. trueppm_api/apps/timetracking/migrations/0003_timeentry_deleted_at_timeentry_deleted_by.py +31 -0
  657. trueppm_api/apps/timetracking/migrations/0004_timeentry_sync_seq_and_more.py +24 -0
  658. trueppm_api/apps/timetracking/migrations/__init__.py +0 -0
  659. trueppm_api/apps/timetracking/models.py +178 -0
  660. trueppm_api/apps/timetracking/serializers.py +175 -0
  661. trueppm_api/apps/timetracking/services.py +211 -0
  662. trueppm_api/apps/timetracking/urls.py +47 -0
  663. trueppm_api/apps/timetracking/views.py +597 -0
  664. trueppm_api/apps/webhooks/__init__.py +0 -0
  665. trueppm_api/apps/webhooks/apps.py +8 -0
  666. trueppm_api/apps/webhooks/backfill.py +90 -0
  667. trueppm_api/apps/webhooks/dispatch.py +279 -0
  668. trueppm_api/apps/webhooks/migrations/0001_initial.py +146 -0
  669. trueppm_api/apps/webhooks/migrations/0001_squashed_0007_sprint_lifecycle_events.py +249 -0
  670. trueppm_api/apps/webhooks/migrations/0002_alter_webhook_events.py +32 -0
  671. trueppm_api/apps/webhooks/migrations/0003_delivery_status_created_idx.py +17 -0
  672. trueppm_api/apps/webhooks/migrations/0004_webhook_program_alter_webhook_project_and_more.py +64 -0
  673. trueppm_api/apps/webhooks/migrations/0005_webhook_delivery_sequence_numbers.py +30 -0
  674. trueppm_api/apps/webhooks/migrations/0006_webhook_format_alter_webhook_events_and_more.py +61 -0
  675. trueppm_api/apps/webhooks/migrations/0007_sprint_lifecycle_events.py +63 -0
  676. trueppm_api/apps/webhooks/migrations/0008_risk_baseline_comment_events.py +73 -0
  677. trueppm_api/apps/webhooks/migrations/0009_encrypt_webhook_secret_and_failure_counters.py +100 -0
  678. trueppm_api/apps/webhooks/migrations/0010_alter_webhook_disabled_at.py +21 -0
  679. trueppm_api/apps/webhooks/migrations/0011_webhookeditor.py +48 -0
  680. trueppm_api/apps/webhooks/migrations/0012_alter_webhook_disabled_at.py +21 -0
  681. trueppm_api/apps/webhooks/migrations/__init__.py +0 -0
  682. trueppm_api/apps/webhooks/models.py +437 -0
  683. trueppm_api/apps/webhooks/serializers.py +326 -0
  684. trueppm_api/apps/webhooks/tasks.py +680 -0
  685. trueppm_api/apps/webhooks/urls.py +22 -0
  686. trueppm_api/apps/webhooks/views.py +533 -0
  687. trueppm_api/apps/workflow_engine/__init__.py +0 -0
  688. trueppm_api/apps/workflow_engine/apps.py +23 -0
  689. trueppm_api/apps/workflow_engine/migrations/0001_initial.py +230 -0
  690. trueppm_api/apps/workflow_engine/migrations/0001_squashed_0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +234 -0
  691. trueppm_api/apps/workflow_engine/migrations/0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +16 -0
  692. trueppm_api/apps/workflow_engine/migrations/0003_workflowoutboxrow_workflow_outbox_recovery_idx.py +21 -0
  693. trueppm_api/apps/workflow_engine/migrations/__init__.py +0 -0
  694. trueppm_api/apps/workflow_engine/models.py +265 -0
  695. trueppm_api/apps/workflow_engine/tasks.py +371 -0
  696. trueppm_api/apps/workspace/__init__.py +0 -0
  697. trueppm_api/apps/workspace/apps.py +10 -0
  698. trueppm_api/apps/workspace/backfill.py +111 -0
  699. trueppm_api/apps/workspace/export.py +254 -0
  700. trueppm_api/apps/workspace/migrations/0001_initial.py +326 -0
  701. trueppm_api/apps/workspace/migrations/0001_squashed_0014_workspacemembership_availability_effective_from_and_more.py +829 -0
  702. trueppm_api/apps/workspace/migrations/0002_remove_workspace_fiscal_year_start_and_more.py +85 -0
  703. trueppm_api/apps/workspace/migrations/0003_workspaceexportjob.py +73 -0
  704. trueppm_api/apps/workspace/migrations/0004_alter_workspaceinvite_status.py +27 -0
  705. trueppm_api/apps/workspace/migrations/0005_workspace_iteration_label_and_more.py +26 -0
  706. trueppm_api/apps/workspace/migrations/0006_alter_groupmembership_unique_together_and_more.py +45 -0
  707. trueppm_api/apps/workspace/migrations/0007_workspace_public_sharing_override_policy.py +21 -0
  708. trueppm_api/apps/workspace/migrations/0008_workspace_mc_history_attribution_audience_and_more.py +44 -0
  709. trueppm_api/apps/workspace/migrations/0009_workspace_methodology_and_more.py +186 -0
  710. trueppm_api/apps/workspace/migrations/0010_workspace_logo.py +38 -0
  711. trueppm_api/apps/workspace/migrations/0011_historicalworkspace_task_duration_change_percent_override_policy_and_more.py +56 -0
  712. trueppm_api/apps/workspace/migrations/0012_historicalworkspace_allowed_attachment_types_and_more.py +64 -0
  713. trueppm_api/apps/workspace/migrations/0013_auditevent.py +70 -0
  714. trueppm_api/apps/workspace/migrations/0014_workspacemembership_availability_effective_from_and_more.py +39 -0
  715. trueppm_api/apps/workspace/migrations/0015_squashed_0027_release_0_4.py +199 -0
  716. trueppm_api/apps/workspace/migrations/0015_trash_restore_1113.py +34 -0
  717. trueppm_api/apps/workspace/migrations/0016_historicalworkspace_calendar_and_more.py +55 -0
  718. trueppm_api/apps/workspace/migrations/0017_historicalworkspace_estimation_scale_and_more.py +38 -0
  719. trueppm_api/apps/workspace/migrations/0018_historicalworkspace_feedback_enabled_and_more.py +32 -0
  720. trueppm_api/apps/workspace/migrations/0019_historicalworkspace_mcp_enabled_and_more.py +22 -0
  721. trueppm_api/apps/workspace/migrations/0020_viewer_ordinal_one.py +34 -0
  722. trueppm_api/apps/workspace/migrations/0021_group_sync_seq_groupmembership_sync_seq_and_more.py +27 -0
  723. trueppm_api/apps/workspace/migrations/0022_historicalworkspace_sprint_picker_ready_only_default_and_more.py +22 -0
  724. trueppm_api/apps/workspace/migrations/0023_alter_auditevent_event_type.py +32 -0
  725. trueppm_api/apps/workspace/migrations/0024_alter_auditevent_event_type.py +33 -0
  726. trueppm_api/apps/workspace/migrations/0025_alter_auditevent_event_type.py +36 -0
  727. trueppm_api/apps/workspace/migrations/0026_alter_auditevent_event_type.py +37 -0
  728. trueppm_api/apps/workspace/migrations/0027_alter_auditevent_event_type.py +42 -0
  729. trueppm_api/apps/workspace/migrations/__init__.py +0 -0
  730. trueppm_api/apps/workspace/models.py +857 -0
  731. trueppm_api/apps/workspace/permissions.py +187 -0
  732. trueppm_api/apps/workspace/serializers.py +590 -0
  733. trueppm_api/apps/workspace/services.py +967 -0
  734. trueppm_api/apps/workspace/signals.py +37 -0
  735. trueppm_api/apps/workspace/tasks.py +616 -0
  736. trueppm_api/apps/workspace/urls.py +113 -0
  737. trueppm_api/apps/workspace/views.py +1581 -0
  738. trueppm_api/asgi.py +49 -0
  739. trueppm_api/celery.py +40 -0
  740. trueppm_api/core/__init__.py +0 -0
  741. trueppm_api/core/admin_site.py +206 -0
  742. trueppm_api/core/auth_views.py +934 -0
  743. trueppm_api/core/constant_time.py +51 -0
  744. trueppm_api/core/csp.py +54 -0
  745. trueppm_api/core/db.py +87 -0
  746. trueppm_api/core/db_session.py +37 -0
  747. trueppm_api/core/demo_read_only.py +314 -0
  748. trueppm_api/core/exception_handlers.py +157 -0
  749. trueppm_api/core/export_downloads.py +56 -0
  750. trueppm_api/core/extension_providers.py +56 -0
  751. trueppm_api/core/extension_signals.py +147 -0
  752. trueppm_api/core/idempotent.py +260 -0
  753. trueppm_api/core/metadata.py +42 -0
  754. trueppm_api/core/middleware.py +51 -0
  755. trueppm_api/core/openapi.py +1221 -0
  756. trueppm_api/core/password_reset.py +513 -0
  757. trueppm_api/core/protect_conflict.py +100 -0
  758. trueppm_api/core/ratelimit.py +148 -0
  759. trueppm_api/core/redis_throttle.py +82 -0
  760. trueppm_api/core/refresh_cookie_policy.py +156 -0
  761. trueppm_api/core/request_body.py +86 -0
  762. trueppm_api/core/security_checks.py +1034 -0
  763. trueppm_api/core/storage_config.py +105 -0
  764. trueppm_api/core/text_decode.py +168 -0
  765. trueppm_api/core/throttling.py +514 -0
  766. trueppm_api/core/valkey.py +214 -0
  767. trueppm_api/core/valkey_cache.py +66 -0
  768. trueppm_api/core/valkey_checks.py +104 -0
  769. trueppm_api/core/valkey_config.py +39 -0
  770. trueppm_api/core/worker_broker_wait.py +149 -0
  771. trueppm_api/core/worker_heartbeat.py +134 -0
  772. trueppm_api/core/worker_selfheal.py +205 -0
  773. trueppm_api/fields.py +36 -0
  774. trueppm_api/permissions.py +30 -0
  775. trueppm_api/py.typed +0 -0
  776. trueppm_api/routing.py +11 -0
  777. trueppm_api/settings/__init__.py +0 -0
  778. trueppm_api/settings/base.py +2605 -0
  779. trueppm_api/settings/dev.py +296 -0
  780. trueppm_api/settings/prod.py +265 -0
  781. trueppm_api/urls.py +217 -0
  782. trueppm_api/workflows/__init__.py +38 -0
  783. trueppm_api/workflows/backends/__init__.py +6 -0
  784. trueppm_api/workflows/backends/default.py +297 -0
  785. trueppm_api/workflows/consumers/__init__.py +11 -0
  786. trueppm_api/workflows/consumers/requeue_failed_task.py +65 -0
  787. trueppm_api/workflows/interface.py +148 -0
  788. trueppm_api/workflows/registry.py +106 -0
  789. trueppm_api/workflows/services.py +90 -0
  790. trueppm_api/wsgi.py +11 -0
  791. trueppm_api-0.4.0.dist-info/METADATA +273 -0
  792. trueppm_api-0.4.0.dist-info/RECORD +799 -0
  793. trueppm_api-0.4.0.dist-info/WHEEL +4 -0
  794. trueppm_api-0.4.0.dist-info/licenses/LICENSE +201 -0
  795. trueppm_api-0.4.0.dist-info/licenses/NOTICE +7 -0
  796. trueppm_api-0.4.0.dist-info/licenses/NOTICES +104 -0
  797. trueppm_api-0.4.0.dist-info/licenses/licenses/GPL-3.0.txt +674 -0
  798. trueppm_api-0.4.0.dist-info/licenses/licenses/LGPL-3.0.txt +165 -0
  799. trueppm_api-0.4.0.dist-info/licenses/licenses/MPL-2.0.txt +373 -0
@@ -0,0 +1,561 @@
1
+ """Subtree classification cascade for ``PATCH /projects/{pk}/tasks/classification/``.
2
+
3
+ ADR-0790, #2735. Applies the two orthogonal hybrid axes — ``governance_class`` and
4
+ ``delivery_mode`` — across a task subtree in one call.
5
+
6
+ The two axes are **not** one choice, and the difference is load-bearing rather than
7
+ cosmetic. ``governance_class`` selects which overlay governs the subtree and carries an
8
+ inherit bit (``parent_governance_inherited``); ``delivery_mode`` selects how work is
9
+ executed, estimated and rolled up, and carries none. ``scrum`` and ``kanban`` are not
10
+ interchangeable either — the rollup engine reads point burndown for one and item
11
+ throughput for the other, and the agile-aware Monte Carlo samples the team velocity
12
+ distribution only for ``scrum``. Collapsing the axes into a single toggle mis-tags every
13
+ Kanban team.
14
+
15
+ Three invariants this module exists to hold:
16
+
17
+ - **Milestones survive a cascade.** ``is_milestone ⟺ delivery_mode='milestone' ⟺
18
+ duration=0`` is normalized on every write path (``TaskSerializer.
19
+ _reconcile_milestone_signals``). Writing an agile delivery mode across a phase would
20
+ silently dissolve every gate inside it, taking the rollup SQL — which keys on
21
+ ``delivery_mode`` — with it. That is never done, under any request flag; see
22
+ :func:`_classify_row`.
23
+ - **Explicit governance survives a cascade.** ``preserve_governance_overrides``
24
+ defaults true. That default is what makes cascading safe to try: a planner who has
25
+ hand-tuned one compliance branch inside an otherwise agile phase can re-declare the
26
+ phase without losing the branch.
27
+ - **The write reaches offline clients.** Every touched row goes through
28
+ ``Task.save()``, never ``QuerySet.update()`` — ``server_version`` and ``sync_seq``
29
+ advance only inside ``VersionedModel.save()`` (ADR-0686), and a bulk update would
30
+ leave the classification permanently invisible to the delta pull (the #2491 class).
31
+
32
+ This is a service function rather than view code so an MCP, management-command, or
33
+ agent caller reaches identical semantics without going through DRF — the "human and
34
+ agent principals are governed identically" claim holds on this path by construction.
35
+ """
36
+
37
+ from __future__ import annotations
38
+
39
+ import dataclasses
40
+ import re
41
+ import uuid
42
+ from typing import TYPE_CHECKING, Any
43
+
44
+ from django.db.models import Q
45
+
46
+ from trueppm_api.apps.access.permissions import can_user_edit_task
47
+ from trueppm_api.apps.projects.refusal_codes import BulkRefusalCode
48
+
49
+ if TYPE_CHECKING:
50
+ from rest_framework.request import Request
51
+
52
+ from trueppm_api.apps.projects.models import Project, Task
53
+
54
+ #: A milestone row was left unclassified on at least one axis. Reported as a *skip*,
55
+ #: never a rejection: a classification crossing a gate is a documented no-op, so one
56
+ #: milestone inside a phase cannot fail the rows around it.
57
+ #:
58
+ #: Bound to the shared enum rather than mirroring ``task_bulk``'s constant by hand
59
+ #: (#3037). The two batch write paths are meant to speak one vocabulary, and the old
60
+ #: comment said so — but a second string literal asserting sameness is exactly what
61
+ #: drifts, and nothing would have failed if it had.
62
+ CODE_MILESTONE_GATE = BulkRefusalCode.MILESTONE_GATE.value
63
+
64
+ #: Maximum rows one cascade may resolve.
65
+ #:
66
+ #: Deliberately **not** shared with ``task_bulk.TASK_BULK_MAX_OPERATIONS`` (500): that
67
+ #: cap bounds rows a human typed into a grid, this one bounds rows the server resolved
68
+ #: from a single click, and 500 sits below a realistic top-level phase. A cap is still
69
+ #: required because §7 of ADR-0790 forces one ``save()`` per changed row, so an
70
+ #: uncapped subtree is an unbounded write inside one transaction. Exceeding it is a 400
71
+ #: naming the count — never a silent truncation, which would report a subtree as
72
+ #: classified when its tail is not.
73
+ TASK_CLASSIFY_MAX_SUBTREE = 2000
74
+
75
+
76
+ class SubtreeTooLarge(Exception):
77
+ """The resolved subtree exceeds :data:`TASK_CLASSIFY_MAX_SUBTREE`.
78
+
79
+ Carries the counts so the view can render an actionable 400 rather than a bare cap
80
+ violation the caller cannot act on.
81
+ """
82
+
83
+ def __init__(self, matched: int, cap: int) -> None:
84
+ self.matched = matched
85
+ self.cap = cap
86
+ super().__init__(f"Subtree resolves {matched} tasks, above the {cap}-task cap.")
87
+
88
+
89
+ class SubtreeNotAuthorable(Exception):
90
+ """The caller may not edit every row in the resolved subtree.
91
+
92
+ Permission on this endpoint is all-or-nothing, which diverges deliberately from
93
+ ``tasks/bulk/``'s per-row rejection. There, the caller typed 38 things and one bad
94
+ row must not discard the other 37. Here they declared *one* thing about a subtree,
95
+ and applying it to the 60% of rows they happen to be assigned leaves the plan
96
+ asserting a split that is not true — worse than refusing (ADR-0790 §6).
97
+
98
+ Carries the count only. No ids: the count is what makes the 403 explainable, and
99
+ enumerating rows adds nothing the caller can act on.
100
+ """
101
+
102
+ def __init__(self, forbidden: int, matched: int, *, detail: str | None = None) -> None:
103
+ self.forbidden = forbidden
104
+ self.matched = matched
105
+ #: ``detail`` exists for the root-only pre-check (#3305), which refuses before
106
+ #: the subtree is resolved and therefore cannot honestly say "n of m tasks" —
107
+ #: it does not know m, and inventing one would re-open the count disclosure
108
+ #: this ordering was changed to close.
109
+ super().__init__(
110
+ detail or f"Your role cannot author {forbidden} of the {matched} tasks in this subtree."
111
+ )
112
+
113
+
114
+ @dataclasses.dataclass(frozen=True)
115
+ class ClassificationSpec:
116
+ """One validated cascade request. Built by ``TaskClassificationSerializer``."""
117
+
118
+ subtree_id: uuid.UUID
119
+ cascade: bool
120
+ governance_class: str | None
121
+ delivery_mode: str | None
122
+ preserve_governance_overrides: bool
123
+ skip_milestones: bool
124
+
125
+
126
+ @dataclasses.dataclass
127
+ class _AxisTally:
128
+ """Per-axis counters. ``overrides_kept`` stays ``None`` on an axis without an
129
+ inherit bit — see :func:`_axis_report`."""
130
+
131
+ applied: int = 0
132
+ unchanged: int = 0
133
+ overrides_kept: int = 0
134
+
135
+
136
+ def resolve_subtree(project: Project, spec: ClassificationSpec) -> list[Task]:
137
+ """Lock and return the subtree root plus, when cascading, its descendants.
138
+
139
+ Raises:
140
+ Task.DoesNotExist: The root is not a live task in this project. The view maps
141
+ this to a 404 rather than leaking whether the id exists elsewhere.
142
+ SubtreeTooLarge: More rows resolved than :data:`TASK_CLASSIFY_MAX_SUBTREE`.
143
+
144
+ Rows are locked with ``select_for_update()`` in ``wbs_path`` order, which is both
145
+ deterministic (so two cascades on overlapping subtrees serialize rather than
146
+ deadlock) and the order the change detection in :func:`cascade_task_classification`
147
+ wants anyway — the root sorts before its own descendants.
148
+
149
+ The root is locked first, in its own statement, before its ``wbs_path`` is read.
150
+ A concurrent reparent moves the path, so reading it unlocked would resolve a
151
+ descendant set for a tree shape that no longer exists by the time the write lands.
152
+ """
153
+ from trueppm_api.apps.projects.models import Task
154
+
155
+ root = Task.objects.select_for_update().get(
156
+ pk=spec.subtree_id, project=project, is_deleted=False
157
+ )
158
+ if not spec.cascade:
159
+ return [root]
160
+
161
+ scope = Q(pk=root.pk)
162
+ if root.wbs_path:
163
+ # A descendant is any live task one or more levels deeper, i.e. whose path
164
+ # starts with "<root path>.". ``__regex`` is the lookup the rest of the
165
+ # codebase uses for ltree paths (see ``serializers.py`` summary detection), so
166
+ # this needs no extra lookup registration. Segments are digits; the escape is
167
+ # a safety net, not a load-bearing guard.
168
+ scope |= Q(wbs_path__regex=rf"^{re.escape(str(root.wbs_path))}\.")
169
+
170
+ matched = Task.objects.filter(scope, project=project, is_deleted=False).count()
171
+ if matched > TASK_CLASSIFY_MAX_SUBTREE:
172
+ raise SubtreeTooLarge(matched, TASK_CLASSIFY_MAX_SUBTREE)
173
+
174
+ return list(
175
+ Task.objects.select_for_update()
176
+ .filter(scope, project=project, is_deleted=False)
177
+ .order_by("wbs_path", "pk")
178
+ )
179
+
180
+
181
+ def assert_graph_feasible(project_id: Any) -> None:
182
+ """Run the ADR-0259 graph guard over the project's edges before recalculation.
183
+
184
+ This endpoint writes no dependency edges, so the guard cannot reject anything the
185
+ cascade itself did. It runs because the cascade calls ``enqueue_recalculate``, and
186
+ ADR-0259's rule is that the guard runs before a bulk write and before
187
+ recalculation.
188
+
189
+ That rule is non-vacuous today rather than defensive ceremony: the seed importer
190
+ still bypasses the guard (**#2589, open**) and project templates seed dependency
191
+ edges through it, so a cyclic graph can genuinely exist in a project — and a
192
+ cascade is very often the first authoring act after a template applies. Catching it
193
+ here surfaces an actionable 400 instead of a CPM worker crash nobody sees.
194
+
195
+ Project-scoped, not subtree-scoped: a cycle can leave the subtree and re-enter it,
196
+ and ``enqueue_recalculate``'s own unit is the project.
197
+
198
+ Raises:
199
+ InfeasibleGraphError: Self-referential or cyclic edges, re-worded to name the
200
+ offending tasks (see :func:`_offending_task_labels`).
201
+ InvalidScheduleInput: The graph is malformed rather than cyclic.
202
+ """
203
+ from trueppm_api.apps.projects.models import Dependency
204
+ from trueppm_api.apps.projects.serializers import _load_project_tasks_and_children_map
205
+ from trueppm_api.apps.scheduling.graph_guard import (
206
+ InfeasibleGraphError,
207
+ validate_task_graph,
208
+ )
209
+
210
+ task_ids, children_map = _load_project_tasks_and_children_map(project_id)
211
+ edges = [
212
+ (str(p), str(s))
213
+ for p, s in Dependency.objects.filter(
214
+ is_deleted=False, predecessor_id__in=[uuid.UUID(t) for t in task_ids]
215
+ ).values_list("predecessor_id", "successor_id")
216
+ ]
217
+ try:
218
+ validate_task_graph(edges, children_map=children_map)
219
+ except InfeasibleGraphError as exc:
220
+ # The guard reasons in opaque node ids; this is the layer that knows they are
221
+ # task pks. Relabelling here rather than passing labels *in* keeps the cost on
222
+ # the refusal path only — the guard blames a handful of ids, and the feasible
223
+ # cascade (every request that is not refused) reads no extra row.
224
+ raise exc.with_labels(_offending_task_labels(project_id, exc.offending)) from exc
225
+
226
+
227
+ def _offending_task_labels(project_id: Any, offending: list[str]) -> dict[str, str]:
228
+ """Resolve blamed task ids to the ``"<WBS> — <name>"`` form the plan shows.
229
+
230
+ That form is not invented here: it is the reference the Schedule already uses
231
+ wherever it names a task outside its own row — the dependency picker, the
232
+ dependencies tab, the task drawer title — so the tasks a refusal names are
233
+ findable by searching for exactly the string it printed.
234
+
235
+ Private on purpose. ``validate_task_graph`` explicitly invites callers to run
236
+ the guard in their own *external*-id space (Jira keys, MS Project uids), and
237
+ relabelling one of those refusals through here would hand a non-UUID to a
238
+ ``UUIDField`` lookup — a Django ``ValidationError`` DRF does not convert, i.e.
239
+ a 500 on what should be a 400. This is only correct for the pk-space guard run
240
+ directly above it.
241
+
242
+ Not a disclosure — verified against the gate, not assumed. The guard is
243
+ project-scoped, and this endpoint requires ``IsProjectMemberWrite`` +
244
+ ``IsProjectPlanAuthor`` on the project, a strict subset of the ``IsProjectMember``
245
+ that already grants ``list``/``retrieve`` on every task in it. So a caller who can
246
+ trip this guard could already have read every name and WBS code it can print; only
247
+ the wording changes. That holds **while** ``Task`` has no row-level visibility gate
248
+ — the dormant ``Visibility`` enum lives on ``Project``/``Program`` only. If per-task
249
+ visibility ever lands, this function becomes a cross-boundary read and must be
250
+ re-scoped; nothing in the pipeline would catch that on its own.
251
+
252
+ Ids that no longer resolve (a row deleted between the refusal and this read) are
253
+ simply absent; the message falls back to the raw id for those alone.
254
+
255
+ Scoped to live rows in *this* project rather than resolving a bare pk. A blamed
256
+ node is always both today — the guard reads only in-project live tasks — but that
257
+ is an invariant of the edge query two functions away, and this lookup is what
258
+ would print the answer if it ever stopped holding.
259
+ """
260
+ from trueppm_api.apps.projects.models import Task
261
+ from trueppm_api.apps.scheduling.graph_guard import nodes_to_label
262
+
263
+ # Only the ids the sentence will actually print. A 5000-task cycle names four of
264
+ # its members, so resolving all 5000 would materialize 4996 rows to discard them.
265
+ rows = Task.objects.filter(
266
+ project_id=project_id, is_deleted=False, pk__in=set(nodes_to_label(offending))
267
+ ).values("id", "name", "wbs_path")
268
+ labels: dict[str, str] = {}
269
+ for row in rows:
270
+ name = (row["name"] or "").strip()
271
+ wbs = str(row["wbs_path"] or "").strip()
272
+ if name and wbs:
273
+ labels[str(row["id"])] = f"{wbs} — {name}"
274
+ elif name:
275
+ labels[str(row["id"])] = name
276
+ elif wbs:
277
+ # A row with no name is still findable by its WBS code, which is the
278
+ # handle the message exists to give — so say so rather than print a
279
+ # dangling separator.
280
+ labels[str(row["id"])] = f"{wbs} — (unnamed task)"
281
+ return labels
282
+
283
+
284
+ def assert_authorable(request: Request, rows: list[Task]) -> None:
285
+ """All-or-nothing per-row edit check (ADR-0790 §6).
286
+
287
+ ``IsProjectPlanAuthor`` decides whether the caller may enter Author mode at all;
288
+ ``can_user_edit_task`` remains authoritative for *which rows* they may touch
289
+ (ADR-0773 §3). Both apply — the project-level gate does not subsume the row-level
290
+ one, which is why a plain Member (who may edit only their assigned tasks) cannot
291
+ re-classify a phase they do not own.
292
+
293
+ Raises:
294
+ SubtreeNotAuthorable: One or more rows fail the per-row bar.
295
+ """
296
+ forbidden = sum(1 for task in rows if not can_user_edit_task(request, task, method="PATCH"))
297
+ if forbidden:
298
+ raise SubtreeNotAuthorable(forbidden, len(rows))
299
+
300
+
301
+ def _apply_governance(
302
+ task: Task,
303
+ *,
304
+ is_root: bool,
305
+ spec: ClassificationSpec,
306
+ tally: _AxisTally,
307
+ ) -> tuple[list[str], list[str]]:
308
+ """Resolve the ``governance_class`` axis for one row.
309
+
310
+ Returns ``(changed, withheld)`` and mutates ``task`` in memory only — saving is the
311
+ caller's decision, exactly as in :func:`_classify_row`.
312
+ """
313
+ changed: list[str] = []
314
+ withheld: list[str] = []
315
+
316
+ if spec.governance_class is not None:
317
+ if task.is_milestone and spec.skip_milestones:
318
+ withheld.append("governance_class")
319
+ elif (
320
+ not is_root
321
+ and spec.preserve_governance_overrides
322
+ and (not task.parent_governance_inherited)
323
+ ):
324
+ # An explicit override: this node declared its own governance rather than
325
+ # taking its parent's. Preserving it is the default, and it is counted so
326
+ # the caller can see what the cascade deliberately did not touch.
327
+ tally.overrides_kept += 1
328
+ else:
329
+ # The root is the declaration point — declaring a subtree's governance IS
330
+ # breaking inheritance from whatever sits above it. Cascaded descendants
331
+ # inherit from the root. A descendant overwritten because the caller sent
332
+ # preserve_governance_overrides=false is no longer an override, so its bit
333
+ # resets to True with everything else.
334
+ target_bit = not is_root
335
+ if (
336
+ task.governance_class != spec.governance_class
337
+ or task.parent_governance_inherited != target_bit
338
+ ):
339
+ task.governance_class = spec.governance_class
340
+ task.parent_governance_inherited = target_bit
341
+ changed += ["governance_class", "parent_governance_inherited"]
342
+ tally.applied += 1
343
+ else:
344
+ tally.unchanged += 1
345
+
346
+ return changed, withheld
347
+
348
+
349
+ def _apply_delivery(
350
+ task: Task,
351
+ *,
352
+ spec: ClassificationSpec,
353
+ tally: _AxisTally,
354
+ ) -> tuple[list[str], list[str]]:
355
+ """Resolve the ``delivery_mode`` axis for one row.
356
+
357
+ Returns ``(changed, withheld)`` and mutates ``task`` in memory only — saving is the
358
+ caller's decision, exactly as in :func:`_classify_row`.
359
+ """
360
+ changed: list[str] = []
361
+ withheld: list[str] = []
362
+
363
+ if spec.delivery_mode is not None:
364
+ if task.is_milestone:
365
+ # Unconditional, and not a flag the caller can waive. is_milestone,
366
+ # delivery_mode='milestone' and duration=0 are three encodings of one
367
+ # fact; writing a non-milestone mode here produces a row the rollup SQL
368
+ # and the CPM engine disagree about. skip_milestones governs the OTHER
369
+ # axis (governance on a gate is invariant-safe — that is what an overlay
370
+ # is); it has no say over this one.
371
+ withheld.append("delivery_mode")
372
+ elif task.delivery_mode != spec.delivery_mode:
373
+ task.delivery_mode = spec.delivery_mode
374
+ changed.append("delivery_mode")
375
+ tally.applied += 1
376
+ else:
377
+ tally.unchanged += 1
378
+
379
+ return changed, withheld
380
+
381
+
382
+ def _classify_row(
383
+ task: Task,
384
+ *,
385
+ is_root: bool,
386
+ spec: ClassificationSpec,
387
+ governance: _AxisTally,
388
+ delivery: _AxisTally,
389
+ skipped: list[dict[str, Any]],
390
+ ) -> list[str]:
391
+ """Compute one row's new classification; return the model fields it changed.
392
+
393
+ Mutates ``task`` in memory only — the caller decides whether to save, so a row
394
+ already at the requested value costs no write, no version bump, and no history row.
395
+
396
+ The two axes are resolved independently and their results concatenated, which is
397
+ what keeps ``changed`` and the skip report's ``axes`` in governance-then-delivery
398
+ order.
399
+ """
400
+ governance_changed, governance_withheld = _apply_governance(
401
+ task, is_root=is_root, spec=spec, tally=governance
402
+ )
403
+ delivery_changed, delivery_withheld = _apply_delivery(task, spec=spec, tally=delivery)
404
+
405
+ changed = governance_changed + delivery_changed
406
+ withheld = governance_withheld + delivery_withheld
407
+
408
+ if withheld:
409
+ skipped.append(
410
+ {
411
+ "id": str(task.pk),
412
+ "code": CODE_MILESTONE_GATE,
413
+ "axes": withheld,
414
+ "message": (
415
+ "Milestone left unclassified on "
416
+ + " and ".join(withheld)
417
+ + " — a classification does not re-type a gate."
418
+ ),
419
+ }
420
+ )
421
+
422
+ return changed
423
+
424
+
425
+ def _axis_report(
426
+ requested: str | None, tally: _AxisTally, *, has_inherit_bit: bool
427
+ ) -> dict[str, Any] | None:
428
+ """One axis's block in the response, or ``None`` if the request omitted the axis.
429
+
430
+ ``overrides_kept`` is ``null`` on an axis with no inherit bit — **not** ``0``.
431
+ Zero reads as "there were none", which is a claim about the data; ``null`` states
432
+ that the count is not computable on this axis, which is a claim about the model,
433
+ and it is the true one. Only ``governance_class`` carries the bit, so only
434
+ governance can report the count (issue #2735 §3).
435
+ """
436
+ if requested is None:
437
+ return None
438
+ return {
439
+ "requested": requested,
440
+ "applied": tally.applied,
441
+ "unchanged": tally.unchanged,
442
+ "overrides_kept": tally.overrides_kept if has_inherit_bit else None,
443
+ "has_inherit_bit": has_inherit_bit,
444
+ }
445
+
446
+
447
+ #: Fields _classify_row can change — also consulted by task_batch_services.py's
448
+ #: undo (the two modules deliberately don't import from each other; see that
449
+ #: module's own copy of this tuple for why).
450
+ CLASSIFICATION_FIELDS = ("governance_class", "parent_governance_inherited", "delivery_mode")
451
+
452
+
453
+ def cascade_task_classification(
454
+ project: Project, request: Request, spec: ClassificationSpec
455
+ ) -> tuple[dict[str, Any], list[uuid.UUID], dict[str, dict[str, Any]]]:
456
+ """Apply one classification cascade.
457
+
458
+ Returns ``(report, written_task_ids, before_after)``. ``before_after`` maps
459
+ ``str(task_id) -> {"before": {...}, "after": {...}}`` for exactly the rows
460
+ written — the ADR-0810 undo ledger's input, built here because this is the
461
+ only point that sees both a row's pre-cascade values and its post-cascade
462
+ ``changed`` fields in the same place.
463
+
464
+ Must be called inside ``transaction.atomic()`` — ``resolve_subtree`` takes row
465
+ locks, and the caller registers the recalculation and broadcast with
466
+ ``transaction.on_commit``.
467
+
468
+ Raises:
469
+ Task.DoesNotExist: the root is not a live task in this project — raised by the
470
+ unlocked root read below, or by :func:`resolve_subtree`.
471
+ SubtreeTooLarge: from :func:`resolve_subtree`.
472
+ SubtreeNotAuthorable: from the root pre-check below, or :func:`assert_authorable`.
473
+ InfeasibleGraphError / InvalidScheduleInput: from :func:`assert_graph_feasible`.
474
+ """
475
+ from trueppm_api.apps.projects.models import Task
476
+
477
+ # Authorization first, so an unauthorized caller never pays for — or learns
478
+ # anything from — the work below. Permission here is all-or-nothing and the root
479
+ # is in every resolved subtree, so authoring the root is a *necessary* condition:
480
+ # refusing on it alone is sound, and it is the only check that can run before the
481
+ # subtree is resolved. Deliberately unlocked and ahead of resolve_subtree, because
482
+ # that call locks the root, resolves descendants and evaluates the size cap — so
483
+ # running it first made a caller with no authorship on the subtree pay for up to
484
+ # 2000 row locks and receive a 400 disclosing the subtree's task count instead of
485
+ # a 403 (#3305).
486
+ #
487
+ # Residual, and narrower by construction: a caller who *can* author the root but
488
+ # not every descendant is still resolved under lock before assert_authorable
489
+ # refuses them below. Closing that too would mean authorizing the descendant set
490
+ # before locking it, which reintroduces a TOCTOU — a row inserted between the
491
+ # unlocked authorization and the locked write would be written unauthorized.
492
+ root_only = Task.objects.get(pk=spec.subtree_id, project=project, is_deleted=False)
493
+ if not can_user_edit_task(request, root_only, method="PATCH"):
494
+ raise SubtreeNotAuthorable(1, 1, detail="Your role cannot author the root of this subtree.")
495
+
496
+ rows = resolve_subtree(project, spec)
497
+ assert_authorable(request, rows)
498
+
499
+ governance = _AxisTally()
500
+ delivery = _AxisTally()
501
+ skipped: list[dict[str, Any]] = []
502
+ written: list[uuid.UUID] = []
503
+ before_after: dict[str, dict[str, Any]] = {}
504
+
505
+ # Two passes. The first mutates instances in memory only and collects what would
506
+ # be written; nothing touches the database. That is what lets the graph guard be
507
+ # skipped entirely when the cascade turns out to be a no-op — its cost scales with
508
+ # PROJECT size (every task row plus every edge), not subtree size, so a repeated
509
+ # identical cascade would otherwise pay a full project scan to write nothing.
510
+ #
511
+ # The "before" snapshot is taken here, ahead of _classify_row, because that
512
+ # function mutates `task` in memory before the caller decides to save — by the
513
+ # time a row reaches `pending` below, its pre-cascade values are already gone
514
+ # from the instance itself.
515
+ pending: list[tuple[Task, list[str]]] = []
516
+ for task in rows:
517
+ before = {field: getattr(task, field) for field in CLASSIFICATION_FIELDS}
518
+ changed = _classify_row(
519
+ task,
520
+ is_root=task.pk == spec.subtree_id,
521
+ spec=spec,
522
+ governance=governance,
523
+ delivery=delivery,
524
+ skipped=skipped,
525
+ )
526
+ if changed:
527
+ pending.append((task, changed))
528
+ before_after[str(task.pk)] = {"before": before}
529
+
530
+ if pending:
531
+ assert_graph_feasible(project.pk)
532
+ for task, changed in pending:
533
+ # known_exists=True: the rows were just SELECTed under a lock, so the
534
+ # INSERT/UPDATE disambiguation probe VersionedModel.save() otherwise runs
535
+ # per row is a guaranteed-True query — 2000 of them for a large phase.
536
+ task.save(update_fields=changed, known_exists=True)
537
+ written.append(task.pk)
538
+ before_after[str(task.pk)]["after"] = {
539
+ field: getattr(task, field) for field in CLASSIFICATION_FIELDS
540
+ }
541
+
542
+ report: dict[str, Any] = {
543
+ "subtree": str(spec.subtree_id),
544
+ "matched": len(rows),
545
+ # Rows actually saved (#3306). Not derivable from the per-axis tallies and
546
+ # not the same number: ``applied`` increments once per row PER AXIS, so a
547
+ # both-axes cascade counts the same row twice, and a milestone withheld on
548
+ # one axis but written on the other is one row appearing in one total only.
549
+ # The governance branch also writes two model columns per increment, so the
550
+ # sum is neither rows nor fields. This is the one count the caller can hold
551
+ # against what they selected and check against the grid.
552
+ "rows_written": len(written),
553
+ "skipped": skipped,
554
+ }
555
+ governance_block = _axis_report(spec.governance_class, governance, has_inherit_bit=True)
556
+ if governance_block is not None:
557
+ report["governance"] = governance_block
558
+ delivery_block = _axis_report(spec.delivery_mode, delivery, has_inherit_bit=False)
559
+ if delivery_block is not None:
560
+ report["delivery_mode"] = delivery_block
561
+ return report, written, before_after
@@ -0,0 +1,95 @@
1
+ """Methodology-aware defaults for a new task's governance/delivery taxonomy (#2667).
2
+
3
+ ``Task.governance_class`` and ``Task.delivery_mode`` (ADR-0036, #407) are purely
4
+ additive fields with model-level defaults of ``FLOW`` / ``WATERFALL`` — chosen so
5
+ every pre-existing row's semantics stayed unchanged when the taxonomy shipped.
6
+ Those literals are also what a brand-new task got on *every* project regardless of
7
+ its own resolved methodology, which produces a self-contradictory dialog on a
8
+ ``WATERFALL`` project (opens on Flow governance — "Agile, sprint- or
9
+ kanban-governed work" — for a project with no sprints or board governance) and a
10
+ correctness bug on ``AGILE`` (delivery mode stays Waterfall, so the task never
11
+ engages point-burndown rollup or agile-aware Monte Carlo).
12
+
13
+ This module resolves the *project-aware* default, consulted only when the client
14
+ omits the field on create (:meth:`TaskSerializer.validate`) — an explicit value
15
+ always wins, on create and update alike, so the documented hybrid seam (a single
16
+ project deliberately holding tasks in different delivery modes/governance
17
+ classes) is untouched. ``HYBRID`` keeps today's literal default (``flow`` /
18
+ ``waterfall``) since it is the preset that already mixes both models.
19
+
20
+ Mirrored client-side in ``packages/web/src/lib/taskClassificationDefaults.ts`` so
21
+ the create dialog opens on the value this module would pick — the two are
22
+ independently maintained (there is no "preview the server default" endpoint) and
23
+ must be changed together. Keep them in sync.
24
+ """
25
+
26
+ from __future__ import annotations
27
+
28
+ from typing import TYPE_CHECKING
29
+
30
+ if TYPE_CHECKING:
31
+ from trueppm_api.apps.projects.models import Project
32
+
33
+
34
+ def resolve_default_governance_class(effective_methodology: str) -> str:
35
+ """The governance class a new task should default to for a project's preset.
36
+
37
+ ``WATERFALL`` → ``gated`` (phase-gate governance matches a project with no
38
+ sprints or board governance). Every other preset (``AGILE``, ``HYBRID``)
39
+ keeps the existing ``flow`` default — ``HYBRID`` deliberately, because it is
40
+ the preset that mixes both governance overlays and has no single "right"
41
+ class to steer toward.
42
+ """
43
+ from trueppm_api.apps.projects.models import GovernanceClass, Methodology
44
+
45
+ if effective_methodology == Methodology.WATERFALL:
46
+ return str(GovernanceClass.GATED)
47
+ return str(GovernanceClass.FLOW)
48
+
49
+
50
+ def resolve_default_delivery_mode(effective_methodology: str, board_cadence: str) -> str:
51
+ """The delivery mode a new task should default to for a project's preset.
52
+
53
+ - ``WATERFALL`` / ``HYBRID`` → ``waterfall`` (HYBRID keeps today's literal
54
+ default — the lossless choice for a preset that mixes delivery modes).
55
+ - ``AGILE`` → ``scrum``, unless the project's board runs continuous-flow
56
+ Kanban with no sprint cadence (``board_cadence == 'continuous'``), in which
57
+ case → ``kanban``.
58
+
59
+ The scrum/kanban split is resolved from ``board_cadence`` rather than an
60
+ extra board-config lookup: ADR-0164 already models "sprint cadence vs.
61
+ continuous flow" as that exact field, and ``scrum`` is the load-bearing
62
+ choice for a brand-new agile project with nothing configured yet — an
63
+ unpointed ``scrum`` task degrades to identical rollup behavior as a
64
+ ``waterfall`` one (``COALESCE(story_points, duration)``), while defaulting to
65
+ ``kanban`` would immediately binarize percent-complete and equal-weight every
66
+ task regardless of size (see #2667 for the full analysis).
67
+ """
68
+ from trueppm_api.apps.projects.models import BoardCadence, DeliveryMode, Methodology
69
+
70
+ if effective_methodology == Methodology.AGILE:
71
+ if board_cadence == BoardCadence.CONTINUOUS:
72
+ return str(DeliveryMode.KANBAN)
73
+ return str(DeliveryMode.SCRUM)
74
+ return str(DeliveryMode.WATERFALL)
75
+
76
+
77
+ def resolve_default_classification(
78
+ project: Project, *, workspace: object | None = None
79
+ ) -> tuple[str, str]:
80
+ """``(governance_class, delivery_mode)`` defaults for a new task on ``project``.
81
+
82
+ Resolves ``project``'s ``effective_methodology`` (workspace → program →
83
+ project precedence, ADR-0107) once and derives both fields from it, so a
84
+ caller needing both never resolves methodology twice. ``workspace`` is
85
+ accepted so a caller iterating many tasks against the same project can pass
86
+ the already-loaded singleton (mirrors the other resolvers in this app);
87
+ otherwise it is loaded here.
88
+ """
89
+ from trueppm_api.apps.projects.methodology import resolve_effective_methodology
90
+
91
+ effective_methodology = resolve_effective_methodology(project, workspace=workspace) # type: ignore[arg-type]
92
+ return (
93
+ resolve_default_governance_class(effective_methodology),
94
+ resolve_default_delivery_mode(effective_methodology, project.board_cadence),
95
+ )