trueppm-api 0.4.0b2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (759) hide show
  1. trueppm_api/__init__.py +14 -0
  2. trueppm_api/apps/access/__init__.py +0 -0
  3. trueppm_api/apps/access/apps.py +19 -0
  4. trueppm_api/apps/access/groups.py +518 -0
  5. trueppm_api/apps/access/management/__init__.py +0 -0
  6. trueppm_api/apps/access/management/commands/__init__.py +0 -0
  7. trueppm_api/apps/access/management/commands/create_admin.py +164 -0
  8. trueppm_api/apps/access/management/commands/migrate_locked.py +96 -0
  9. trueppm_api/apps/access/management/commands/revoke_api_tokens.py +209 -0
  10. trueppm_api/apps/access/migrations/0001_initial.py +61 -0
  11. trueppm_api/apps/access/migrations/0001_squashed_0012_programmembership_role_title.py +167 -0
  12. trueppm_api/apps/access/migrations/0002_alter_projectmembership_id.py +29 -0
  13. trueppm_api/apps/access/migrations/0003_projectmembership_soft_delete.py +24 -0
  14. trueppm_api/apps/access/migrations/0004_alter_projectmembership_role.py +34 -0
  15. trueppm_api/apps/access/migrations/0005_program_entity_and_membership.py +76 -0
  16. trueppm_api/apps/access/migrations/0006_role_ordinal_spacing.py +113 -0
  17. trueppm_api/apps/access/migrations/0007_projectmembership_joined_at_and_more.py +35 -0
  18. trueppm_api/apps/access/migrations/0008_projectmembership_source_group.py +25 -0
  19. trueppm_api/apps/access/migrations/0009_projectmembership_pm_proj_serverver_idx.py +20 -0
  20. trueppm_api/apps/access/migrations/0010_programmembership_joined_at_and_more.py +38 -0
  21. trueppm_api/apps/access/migrations/0011_alter_programmembership_unique_together_and_more.py +36 -0
  22. trueppm_api/apps/access/migrations/0012_programmembership_role_title.py +17 -0
  23. trueppm_api/apps/access/migrations/0013_userdefinedmentiongroup.py +82 -0
  24. trueppm_api/apps/access/migrations/0014_programmembership_progm_serverver_idx.py +19 -0
  25. trueppm_api/apps/access/migrations/0015_programuserdefinedmentiongroup.py +86 -0
  26. trueppm_api/apps/access/migrations/0016_externalstakeholder.py +67 -0
  27. trueppm_api/apps/access/migrations/0017_viewer_ordinal_one.py +132 -0
  28. trueppm_api/apps/access/migrations/0018_programmembership_sync_seq_and_more.py +52 -0
  29. trueppm_api/apps/access/migrations/0019_programmembership_reinstated_at_and_more.py +22 -0
  30. trueppm_api/apps/access/migrations/__init__.py +0 -0
  31. trueppm_api/apps/access/models.py +588 -0
  32. trueppm_api/apps/access/permissions.py +2837 -0
  33. trueppm_api/apps/access/serializers.py +946 -0
  34. trueppm_api/apps/access/services.py +667 -0
  35. trueppm_api/apps/access/signals.py +103 -0
  36. trueppm_api/apps/access/tasks.py +50 -0
  37. trueppm_api/apps/access/throttles.py +95 -0
  38. trueppm_api/apps/access/urls.py +191 -0
  39. trueppm_api/apps/access/views.py +1931 -0
  40. trueppm_api/apps/agents/__init__.py +6 -0
  41. trueppm_api/apps/agents/apps.py +11 -0
  42. trueppm_api/apps/agents/canonical.py +73 -0
  43. trueppm_api/apps/agents/deferred.py +108 -0
  44. trueppm_api/apps/agents/management/__init__.py +0 -0
  45. trueppm_api/apps/agents/management/commands/__init__.py +0 -0
  46. trueppm_api/apps/agents/management/commands/audit_prune.py +137 -0
  47. trueppm_api/apps/agents/management/commands/audit_verify.py +101 -0
  48. trueppm_api/apps/agents/middleware.py +44 -0
  49. trueppm_api/apps/agents/migrations/0001_initial.py +231 -0
  50. trueppm_api/apps/agents/migrations/0002_agentactioncheckpoint.py +71 -0
  51. trueppm_api/apps/agents/migrations/0003_agentactionrefusaldetail.py +58 -0
  52. trueppm_api/apps/agents/migrations/__init__.py +0 -0
  53. trueppm_api/apps/agents/models.py +371 -0
  54. trueppm_api/apps/agents/refusal.py +103 -0
  55. trueppm_api/apps/agents/serializers.py +85 -0
  56. trueppm_api/apps/agents/services.py +397 -0
  57. trueppm_api/apps/agents/signals.py +30 -0
  58. trueppm_api/apps/agents/urls.py +15 -0
  59. trueppm_api/apps/agents/views.py +108 -0
  60. trueppm_api/apps/csvimport/__init__.py +12 -0
  61. trueppm_api/apps/csvimport/apps.py +11 -0
  62. trueppm_api/apps/csvimport/mapping.py +363 -0
  63. trueppm_api/apps/csvimport/migrations/0001_initial.py +74 -0
  64. trueppm_api/apps/csvimport/migrations/0002_schedule_outline_batch_operations.py +37 -0
  65. trueppm_api/apps/csvimport/migrations/0003_csvimportrequest_date_order_and_more.py +26 -0
  66. trueppm_api/apps/csvimport/migrations/__init__.py +0 -0
  67. trueppm_api/apps/csvimport/models.py +111 -0
  68. trueppm_api/apps/csvimport/parser.py +1825 -0
  69. trueppm_api/apps/csvimport/services.py +48 -0
  70. trueppm_api/apps/csvimport/tasks.py +418 -0
  71. trueppm_api/apps/csvimport/template.py +37 -0
  72. trueppm_api/apps/csvimport/urls.py +50 -0
  73. trueppm_api/apps/csvimport/views.py +793 -0
  74. trueppm_api/apps/history/__init__.py +0 -0
  75. trueppm_api/apps/history/apps.py +12 -0
  76. trueppm_api/apps/history/changelog.py +359 -0
  77. trueppm_api/apps/history/diff_policy.py +177 -0
  78. trueppm_api/apps/history/serializers.py +104 -0
  79. trueppm_api/apps/history/signals.py +82 -0
  80. trueppm_api/apps/history/tasks.py +105 -0
  81. trueppm_api/apps/history/urls.py +38 -0
  82. trueppm_api/apps/history/views.py +443 -0
  83. trueppm_api/apps/idempotency/__init__.py +0 -0
  84. trueppm_api/apps/idempotency/apps.py +11 -0
  85. trueppm_api/apps/idempotency/migrations/0001_initial.py +71 -0
  86. trueppm_api/apps/idempotency/migrations/__init__.py +0 -0
  87. trueppm_api/apps/idempotency/mixins.py +226 -0
  88. trueppm_api/apps/idempotency/models.py +75 -0
  89. trueppm_api/apps/idempotency/tasks.py +61 -0
  90. trueppm_api/apps/integrations/__init__.py +13 -0
  91. trueppm_api/apps/integrations/apps.py +59 -0
  92. trueppm_api/apps/integrations/connections.py +806 -0
  93. trueppm_api/apps/integrations/encryption.py +92 -0
  94. trueppm_api/apps/integrations/external_sources.py +837 -0
  95. trueppm_api/apps/integrations/git_automation_services.py +231 -0
  96. trueppm_api/apps/integrations/git_webhook_auth.py +243 -0
  97. trueppm_api/apps/integrations/http.py +403 -0
  98. trueppm_api/apps/integrations/me_work.py +218 -0
  99. trueppm_api/apps/integrations/migrations/0001_initial.py +64 -0
  100. trueppm_api/apps/integrations/migrations/0001_squashed_0006_tasklink_description_tasklink_preview_type_and_more.py +187 -0
  101. trueppm_api/apps/integrations/migrations/0002_tasklink.py +67 -0
  102. trueppm_api/apps/integrations/migrations/0003_tasklink_tasklink_serverver_idx.py +17 -0
  103. trueppm_api/apps/integrations/migrations/0004_tasklink_custom_title_tasklink_labels.py +25 -0
  104. trueppm_api/apps/integrations/migrations/0005_boardautomation.py +56 -0
  105. trueppm_api/apps/integrations/migrations/0006_tasklink_description_tasklink_preview_type_and_more.py +40 -0
  106. trueppm_api/apps/integrations/migrations/0007_tasklink_created_at.py +23 -0
  107. trueppm_api/apps/integrations/migrations/0008_integrationcredential_config_externalworkitem.py +66 -0
  108. trueppm_api/apps/integrations/migrations/0009_externalsyncrequest_and_more.py +96 -0
  109. trueppm_api/apps/integrations/migrations/0010_externalworkitem_due_date.py +17 -0
  110. trueppm_api/apps/integrations/migrations/0011_tasklink_sync_seq_tasklink_tasklink_syncseq_idx.py +22 -0
  111. trueppm_api/apps/integrations/migrations/0012_boardautomation_last_delivery_at_and_more.py +42 -0
  112. trueppm_api/apps/integrations/migrations/0013_externalsyncrequest_attempt_count_and_more.py +26 -0
  113. trueppm_api/apps/integrations/migrations/__init__.py +0 -0
  114. trueppm_api/apps/integrations/models.py +622 -0
  115. trueppm_api/apps/integrations/notification_channels.py +57 -0
  116. trueppm_api/apps/integrations/opengraph.py +229 -0
  117. trueppm_api/apps/integrations/outgoing.py +201 -0
  118. trueppm_api/apps/integrations/providers.py +519 -0
  119. trueppm_api/apps/integrations/registry.py +345 -0
  120. trueppm_api/apps/integrations/serializers.py +453 -0
  121. trueppm_api/apps/integrations/services.py +171 -0
  122. trueppm_api/apps/integrations/tasks.py +731 -0
  123. trueppm_api/apps/integrations/throttles.py +165 -0
  124. trueppm_api/apps/integrations/urls.py +77 -0
  125. trueppm_api/apps/integrations/views.py +1106 -0
  126. trueppm_api/apps/jiraimport/__init__.py +8 -0
  127. trueppm_api/apps/jiraimport/apps.py +11 -0
  128. trueppm_api/apps/jiraimport/migrations/0001_initial.py +72 -0
  129. trueppm_api/apps/jiraimport/migrations/__init__.py +0 -0
  130. trueppm_api/apps/jiraimport/models.py +70 -0
  131. trueppm_api/apps/jiraimport/parser.py +350 -0
  132. trueppm_api/apps/jiraimport/services.py +46 -0
  133. trueppm_api/apps/jiraimport/tasks.py +277 -0
  134. trueppm_api/apps/jiraimport/urls.py +15 -0
  135. trueppm_api/apps/jiraimport/views.py +153 -0
  136. trueppm_api/apps/msproject/__init__.py +0 -0
  137. trueppm_api/apps/msproject/apps.py +11 -0
  138. trueppm_api/apps/msproject/dataclasses.py +153 -0
  139. trueppm_api/apps/msproject/exporter.py +500 -0
  140. trueppm_api/apps/msproject/extended_attributes.py +77 -0
  141. trueppm_api/apps/msproject/importer.py +1095 -0
  142. trueppm_api/apps/msproject/migrations/0001_import_request.py +77 -0
  143. trueppm_api/apps/msproject/migrations/0001_squashed_0002_importrequest_creates_project.py +80 -0
  144. trueppm_api/apps/msproject/migrations/0002_importrequest_creates_project.py +15 -0
  145. trueppm_api/apps/msproject/migrations/__init__.py +0 -0
  146. trueppm_api/apps/msproject/models.py +81 -0
  147. trueppm_api/apps/msproject/parser.py +1231 -0
  148. trueppm_api/apps/msproject/serializers.py +136 -0
  149. trueppm_api/apps/msproject/services.py +58 -0
  150. trueppm_api/apps/msproject/tasks.py +436 -0
  151. trueppm_api/apps/msproject/urls.py +39 -0
  152. trueppm_api/apps/msproject/views.py +531 -0
  153. trueppm_api/apps/notifications/__init__.py +7 -0
  154. trueppm_api/apps/notifications/apps.py +15 -0
  155. trueppm_api/apps/notifications/backfill.py +79 -0
  156. trueppm_api/apps/notifications/categories.py +112 -0
  157. trueppm_api/apps/notifications/delivery_limits.py +258 -0
  158. trueppm_api/apps/notifications/digests.py +447 -0
  159. trueppm_api/apps/notifications/email_backend.py +330 -0
  160. trueppm_api/apps/notifications/email_health.py +128 -0
  161. trueppm_api/apps/notifications/migrations/0001_initial.py +207 -0
  162. trueppm_api/apps/notifications/migrations/0001_squashed_0006_notification_task.py +294 -0
  163. trueppm_api/apps/notifications/migrations/0002_project_notification_preference.py +67 -0
  164. trueppm_api/apps/notifications/migrations/0003_projectnotificationpreference_paused.py +17 -0
  165. trueppm_api/apps/notifications/migrations/0004_clean_unknown_matrix_keys.py +30 -0
  166. trueppm_api/apps/notifications/migrations/0005_notification_body_notification_event_type_and_more.py +27 -0
  167. trueppm_api/apps/notifications/migrations/0006_notification_task.py +25 -0
  168. trueppm_api/apps/notifications/migrations/0007_workspaceemailsettings.py +80 -0
  169. trueppm_api/apps/notifications/migrations/0008_notification_snoozed_until.py +17 -0
  170. trueppm_api/apps/notifications/migrations/0009_usernotificationsettings.py +43 -0
  171. trueppm_api/apps/notifications/migrations/0010_projectnotificationpreference_schema_version.py +20 -0
  172. trueppm_api/apps/notifications/migrations/0011_usernotificationsettings_digest_hour_and_more.py +96 -0
  173. trueppm_api/apps/notifications/migrations/__init__.py +0 -0
  174. trueppm_api/apps/notifications/models.py +1197 -0
  175. trueppm_api/apps/notifications/receivers.py +155 -0
  176. trueppm_api/apps/notifications/schema_migrations.py +56 -0
  177. trueppm_api/apps/notifications/serializers.py +961 -0
  178. trueppm_api/apps/notifications/services.py +1451 -0
  179. trueppm_api/apps/notifications/tasks.py +711 -0
  180. trueppm_api/apps/notifications/throttles.py +98 -0
  181. trueppm_api/apps/notifications/urls.py +97 -0
  182. trueppm_api/apps/notifications/views.py +904 -0
  183. trueppm_api/apps/observability/__init__.py +0 -0
  184. trueppm_api/apps/observability/apps.py +31 -0
  185. trueppm_api/apps/observability/logging.py +213 -0
  186. trueppm_api/apps/observability/migrations/0001_initial.py +35 -0
  187. trueppm_api/apps/observability/migrations/0001_squashed_0003_alter_purgerun_trigger.py +154 -0
  188. trueppm_api/apps/observability/migrations/0002_purgerun_retentionpolicy_retentionschedule.py +131 -0
  189. trueppm_api/apps/observability/migrations/0003_alter_purgerun_trigger.py +21 -0
  190. trueppm_api/apps/observability/migrations/0004_alter_retentionpolicy_key.py +28 -0
  191. trueppm_api/apps/observability/migrations/__init__.py +0 -0
  192. trueppm_api/apps/observability/models.py +141 -0
  193. trueppm_api/apps/observability/otel/__init__.py +45 -0
  194. trueppm_api/apps/observability/otel/attributes.py +247 -0
  195. trueppm_api/apps/observability/otel/export_health.py +533 -0
  196. trueppm_api/apps/observability/otel/instrumentation.py +351 -0
  197. trueppm_api/apps/observability/otel/metrics.py +551 -0
  198. trueppm_api/apps/observability/otel/provider.py +511 -0
  199. trueppm_api/apps/observability/otel/request_attributes.py +204 -0
  200. trueppm_api/apps/observability/purge_registry.py +124 -0
  201. trueppm_api/apps/observability/retention.py +139 -0
  202. trueppm_api/apps/observability/selectors.py +969 -0
  203. trueppm_api/apps/observability/serializers.py +142 -0
  204. trueppm_api/apps/observability/services.py +407 -0
  205. trueppm_api/apps/observability/tasks.py +266 -0
  206. trueppm_api/apps/observability/urls.py +27 -0
  207. trueppm_api/apps/observability/views.py +523 -0
  208. trueppm_api/apps/profiles/__init__.py +0 -0
  209. trueppm_api/apps/profiles/apps.py +15 -0
  210. trueppm_api/apps/profiles/constants.py +54 -0
  211. trueppm_api/apps/profiles/migrations/0001_initial.py +55 -0
  212. trueppm_api/apps/profiles/migrations/0001_squashed_0004_userprofile_role_context.py +125 -0
  213. trueppm_api/apps/profiles/migrations/0002_userprofile_hidden_views.py +17 -0
  214. trueppm_api/apps/profiles/migrations/0003_projectvisit.py +63 -0
  215. trueppm_api/apps/profiles/migrations/0004_userprofile_role_context.py +26 -0
  216. trueppm_api/apps/profiles/migrations/0005_userprofile_schedule_in_deliver.py +20 -0
  217. trueppm_api/apps/profiles/migrations/0006_userprofile_date_format_userprofile_timezone.py +36 -0
  218. trueppm_api/apps/profiles/migrations/0007_userpin.py +91 -0
  219. trueppm_api/apps/profiles/migrations/0008_remove_userprofile_schedule_in_deliver.py +71 -0
  220. trueppm_api/apps/profiles/migrations/0009_auth_user_email_upper_idx.py +81 -0
  221. trueppm_api/apps/profiles/migrations/__init__.py +0 -0
  222. trueppm_api/apps/profiles/models.py +276 -0
  223. trueppm_api/apps/profiles/serializers.py +199 -0
  224. trueppm_api/apps/profiles/services.py +466 -0
  225. trueppm_api/apps/profiles/views.py +124 -0
  226. trueppm_api/apps/projects/__init__.py +0 -0
  227. trueppm_api/apps/projects/actual_date_rules.py +163 -0
  228. trueppm_api/apps/projects/amend.py +133 -0
  229. trueppm_api/apps/projects/apps.py +38 -0
  230. trueppm_api/apps/projects/asset_feed.py +459 -0
  231. trueppm_api/apps/projects/asset_views.py +317 -0
  232. trueppm_api/apps/projects/attachment_policy.py +278 -0
  233. trueppm_api/apps/projects/authentication.py +449 -0
  234. trueppm_api/apps/projects/backfill.py +287 -0
  235. trueppm_api/apps/projects/backlog_services.py +324 -0
  236. trueppm_api/apps/projects/backlog_views.py +302 -0
  237. trueppm_api/apps/projects/batch_operation_views.py +264 -0
  238. trueppm_api/apps/projects/blocker_services.py +510 -0
  239. trueppm_api/apps/projects/board_activity.py +462 -0
  240. trueppm_api/apps/projects/board_activity_views.py +186 -0
  241. trueppm_api/apps/projects/board_lanes.py +134 -0
  242. trueppm_api/apps/projects/bulk_settings.py +194 -0
  243. trueppm_api/apps/projects/bundled_templates.py +215 -0
  244. trueppm_api/apps/projects/calendar_settings.py +180 -0
  245. trueppm_api/apps/projects/ceremony_views.py +244 -0
  246. trueppm_api/apps/projects/commit_moment.py +204 -0
  247. trueppm_api/apps/projects/config_notice.py +1325 -0
  248. trueppm_api/apps/projects/custom_field_values.py +266 -0
  249. trueppm_api/apps/projects/decisions_services.py +93 -0
  250. trueppm_api/apps/projects/decisions_views.py +94 -0
  251. trueppm_api/apps/projects/estimation_scale.py +126 -0
  252. trueppm_api/apps/projects/export_bundle.py +353 -0
  253. trueppm_api/apps/projects/fixtures/seeds/atlas-platform-launch.json +4256 -0
  254. trueppm_api/apps/projects/fixtures/seeds/aurora-mobile-app.json +2000 -0
  255. trueppm_api/apps/projects/fixtures/seeds/bayside-civic-center.json +1825 -0
  256. trueppm_api/apps/projects/fixtures/seeds/ga-launch.json +1905 -0
  257. trueppm_api/apps/projects/fixtures/seeds/helios-crm-replacement.json +1409 -0
  258. trueppm_api/apps/projects/guardrail_policy_source.py +269 -0
  259. trueppm_api/apps/projects/inbound_sync.py +555 -0
  260. trueppm_api/apps/projects/iteration_label.py +85 -0
  261. trueppm_api/apps/projects/lifecycle.py +191 -0
  262. trueppm_api/apps/projects/management/__init__.py +0 -0
  263. trueppm_api/apps/projects/management/commands/__init__.py +0 -0
  264. trueppm_api/apps/projects/management/commands/backfill_in_progress_status.py +92 -0
  265. trueppm_api/apps/projects/management/commands/create_demo_share_link.py +368 -0
  266. trueppm_api/apps/projects/management/commands/export_program.py +50 -0
  267. trueppm_api/apps/projects/management/commands/import_seed.py +165 -0
  268. trueppm_api/apps/projects/management/commands/load_sample_project.py +247 -0
  269. trueppm_api/apps/projects/management/commands/seed_capacity.py +405 -0
  270. trueppm_api/apps/projects/management/commands/seed_integration_fixtures.py +178 -0
  271. trueppm_api/apps/projects/management/commands/shift_sample_dates.py +120 -0
  272. trueppm_api/apps/projects/mcp_settings.py +305 -0
  273. trueppm_api/apps/projects/methodology.py +178 -0
  274. trueppm_api/apps/projects/migrations/0001_initial.py +208 -0
  275. trueppm_api/apps/projects/migrations/0001_squashed_0094_project_decisions_policy.py +5595 -0
  276. trueppm_api/apps/projects/migrations/0002_alter_uuid_pk_serialize_false.py +57 -0
  277. trueppm_api/apps/projects/migrations/0003_rename_task_project_index.py +25 -0
  278. trueppm_api/apps/projects/migrations/0004_soft_delete_and_dependency_versioned.py +70 -0
  279. trueppm_api/apps/projects/migrations/0005_task_assignee.py +33 -0
  280. trueppm_api/apps/projects/migrations/0006_task_utilization_index.py +31 -0
  281. trueppm_api/apps/projects/migrations/0007_task_is_milestone.py +28 -0
  282. trueppm_api/apps/projects/migrations/0008_baseline.py +119 -0
  283. trueppm_api/apps/projects/migrations/0009_historicaldependency_historicalproject_and_more.py +210 -0
  284. trueppm_api/apps/projects/migrations/0010_risk.py +229 -0
  285. trueppm_api/apps/projects/migrations/0011_task_status.py +27 -0
  286. trueppm_api/apps/projects/migrations/0012_task_status.py +27 -0
  287. trueppm_api/apps/projects/migrations/0013_task_planned_start.py +24 -0
  288. trueppm_api/apps/projects/migrations/0014_short_hex_ids.py +74 -0
  289. trueppm_api/apps/projects/migrations/0015_backfill_short_ids.py +48 -0
  290. trueppm_api/apps/projects/migrations/0016_task_actual_dates.py +42 -0
  291. trueppm_api/apps/projects/migrations/0017_boardcolumnconfig.py +43 -0
  292. trueppm_api/apps/projects/migrations/0018_estimation_governance.py +62 -0
  293. trueppm_api/apps/projects/migrations/0019_backfill_wbs_paths.py +28 -0
  294. trueppm_api/apps/projects/migrations/0020_task_status_5col.py +65 -0
  295. trueppm_api/apps/projects/migrations/0021_boardsavedview.py +64 -0
  296. trueppm_api/apps/projects/migrations/0022_task_status_changed_at_priority_rank.py +35 -0
  297. trueppm_api/apps/projects/migrations/0023_risk_framework_fields.py +96 -0
  298. trueppm_api/apps/projects/migrations/0024_riskcomment.py +52 -0
  299. trueppm_api/apps/projects/migrations/0025_sprint.py +352 -0
  300. trueppm_api/apps/projects/migrations/0026_project_methodology.py +30 -0
  301. trueppm_api/apps/projects/migrations/0027_sprint_retro.py +89 -0
  302. trueppm_api/apps/projects/migrations/0028_normalize_notes_field.py +42 -0
  303. trueppm_api/apps/projects/migrations/0029_complete_implies_full_progress.py +38 -0
  304. trueppm_api/apps/projects/migrations/0030_review_implies_full_progress.py +37 -0
  305. trueppm_api/apps/projects/migrations/0031_task_remaining_points.py +22 -0
  306. trueppm_api/apps/projects/migrations/0032_task_is_subtask_and_sprint_scope_change.py +90 -0
  307. trueppm_api/apps/projects/migrations/0033_task_my_work_index.py +38 -0
  308. trueppm_api/apps/projects/migrations/0034_inbound_task_sync.py +313 -0
  309. trueppm_api/apps/projects/migrations/0035_retro_versioned_and_suggested_assignee.py +173 -0
  310. trueppm_api/apps/projects/migrations/0036_program_entity_and_membership.py +157 -0
  311. trueppm_api/apps/projects/migrations/0037_sprint_capacity_points.py +20 -0
  312. trueppm_api/apps/projects/migrations/0038_taskcomment_commentreaction_commentacknowledgement_and_more.py +233 -0
  313. trueppm_api/apps/projects/migrations/0039_apitoken_and_more.py +88 -0
  314. trueppm_api/apps/projects/migrations/0040_program_general_fields.py +119 -0
  315. trueppm_api/apps/projects/migrations/0041_ceremony_template_and_phase_gate_config.py +210 -0
  316. trueppm_api/apps/projects/migrations/0041_project_general_fields.py +106 -0
  317. trueppm_api/apps/projects/migrations/0042_merge_20260522_0410.py +12 -0
  318. trueppm_api/apps/projects/migrations/0042_program_rollup_config.py +95 -0
  319. trueppm_api/apps/projects/migrations/0042_workflow_color_and_custom_fields.py +94 -0
  320. trueppm_api/apps/projects/migrations/0043_merge_20260522_1118.py +10 -0
  321. trueppm_api/apps/projects/migrations/0044_merge_20260522_rollup.py +10 -0
  322. trueppm_api/apps/projects/migrations/0044_project_archive_program_close.py +119 -0
  323. trueppm_api/apps/projects/migrations/0045_program_risk_policy.py +78 -0
  324. trueppm_api/apps/projects/migrations/0046_merge_20260523_2248.py +12 -0
  325. trueppm_api/apps/projects/migrations/0047_apitokenauditentry_program_and_more.py +55 -0
  326. trueppm_api/apps/projects/migrations/0048_historicalprogram_color_program_color.py +32 -0
  327. trueppm_api/apps/projects/migrations/0049_backlog_item.py +112 -0
  328. trueppm_api/apps/projects/migrations/0050_backlog_item_trgm_search.py +26 -0
  329. trueppm_api/apps/projects/migrations/0051_alter_calendar_working_days_alter_dependency_lag_and_more.py +144 -0
  330. trueppm_api/apps/projects/migrations/0052_recurring_tasks.py +278 -0
  331. trueppm_api/apps/projects/migrations/0053_dependency_dep_pred_serverver_idx_and_more.py +56 -0
  332. trueppm_api/apps/projects/migrations/0054_sprintscopechange_goal_impact_and_more.py +117 -0
  333. trueppm_api/apps/projects/migrations/0055_historicaltask_sprint_pending_and_more.py +51 -0
  334. trueppm_api/apps/projects/migrations/0056_historicalproject_prioritization_model_and_more.py +276 -0
  335. trueppm_api/apps/projects/migrations/0057_historicalsprint_binding_committed_snapshot_and_more.py +58 -0
  336. trueppm_api/apps/projects/migrations/0058_historicaltask_delivery_mode_and_more.py +72 -0
  337. trueppm_api/apps/projects/migrations/0059_forecastsnapshot.py +80 -0
  338. trueppm_api/apps/projects/migrations/0060_historicalprojectsignalprivacypolicy_and_more.py +90 -0
  339. trueppm_api/apps/projects/migrations/0061_baselinetask_story_points.py +17 -0
  340. trueppm_api/apps/projects/migrations/0062_project_is_sample.py +22 -0
  341. trueppm_api/apps/projects/migrations/0063_historicalsprint_wip_limit_sprint_wip_limit.py +22 -0
  342. trueppm_api/apps/projects/migrations/0064_sprinttaskoutcome.py +97 -0
  343. trueppm_api/apps/projects/migrations/0065_historicalsprint_goal_outcome_sprint_goal_outcome.py +32 -0
  344. trueppm_api/apps/projects/migrations/0066_historicalproject_lead_project_lead.py +38 -0
  345. trueppm_api/apps/projects/migrations/0067_sprint_exclude_from_velocity.py +22 -0
  346. trueppm_api/apps/projects/migrations/0068_project_recalculated_at.py +23 -0
  347. trueppm_api/apps/projects/migrations/0069_iteration_label.py +22 -0
  348. trueppm_api/apps/projects/migrations/0070_historicalprogram_iteration_label_and_more.py +32 -0
  349. trueppm_api/apps/projects/migrations/0071_backfill_iteration_label_inherit.py +35 -0
  350. trueppm_api/apps/projects/migrations/0072_pulseresponse_retroboarditem.py +129 -0
  351. trueppm_api/apps/projects/migrations/0073_sprinttaskoutcome_demo_ready.py +17 -0
  352. trueppm_api/apps/projects/migrations/0074_historicaltask_blocked_reason_task_blocked_reason.py +30 -0
  353. trueppm_api/apps/projects/migrations/0075_risk_decimal_short_id.py +42 -0
  354. trueppm_api/apps/projects/migrations/0076_sprinttaskoutcome_review_polish.py +45 -0
  355. trueppm_api/apps/projects/migrations/0077_task_blocker_fields.py +111 -0
  356. trueppm_api/apps/projects/migrations/0078_alter_risktask_unique_together_and_more.py +22 -0
  357. trueppm_api/apps/projects/migrations/0079_historicalproject_status_date_project_status_date.py +22 -0
  358. trueppm_api/apps/projects/migrations/0080_alter_historicaltask_type_alter_task_type.py +46 -0
  359. trueppm_api/apps/projects/migrations/0081_historicalprogram_allow_guests_and_more.py +52 -0
  360. trueppm_api/apps/projects/migrations/0082_project_last_sync_version.py +90 -0
  361. trueppm_api/apps/projects/migrations/0083_historicalprogram_mc_history_attribution_audience_and_more.py +108 -0
  362. trueppm_api/apps/projects/migrations/0084_tasknote.py +71 -0
  363. trueppm_api/apps/projects/migrations/0085_sprint_ix_sprint_velocity.py +33 -0
  364. trueppm_api/apps/projects/migrations/0086_board_card_search_trgm.py +33 -0
  365. trueppm_api/apps/projects/migrations/0087_historicalprogram_task_duration_change_percent_policy_and_more.py +155 -0
  366. trueppm_api/apps/projects/migrations/0088_historicalprogram_allowed_attachment_types_and_more.py +61 -0
  367. trueppm_api/apps/projects/migrations/0089_signalceilingraiseproposal_signalceilingraisevote_and_more.py +157 -0
  368. trueppm_api/apps/projects/migrations/0090_historicaltask_proj_histdate_index.py +44 -0
  369. trueppm_api/apps/projects/migrations/0091_project_board_cadence.py +39 -0
  370. trueppm_api/apps/projects/migrations/0092_program_target_date.py +22 -0
  371. trueppm_api/apps/projects/migrations/0093_cross_project_dependency_consent.py +58 -0
  372. trueppm_api/apps/projects/migrations/0094_project_decisions_policy.py +90 -0
  373. trueppm_api/apps/projects/migrations/0095_poker_session_vote.py +127 -0
  374. trueppm_api/apps/projects/migrations/0095_squashed_0097_release_0_3.py +225 -0
  375. trueppm_api/apps/projects/migrations/0096_crossprojectslipconflict.py +91 -0
  376. trueppm_api/apps/projects/migrations/0097_backlogitem_backlogitem_tags_gin_and_more.py +32 -0
  377. trueppm_api/apps/projects/migrations/0098_historicalproject_show_baselines_and_more.py +52 -0
  378. trueppm_api/apps/projects/migrations/0099_apitoken_scopes.py +48 -0
  379. trueppm_api/apps/projects/migrations/0100_task_dep_serverver_idx_concurrent.py +61 -0
  380. trueppm_api/apps/projects/migrations/0101_dependency_deleted_at_task_deleted_at.py +22 -0
  381. trueppm_api/apps/projects/migrations/0102_project_deleted_at_project_proj_isdel_deletedat_idx.py +25 -0
  382. trueppm_api/apps/projects/migrations/0103_taskdurationchangeevent_task_dur_evt_sprint_idx.py +18 -0
  383. trueppm_api/apps/projects/migrations/0104_historicalproject_stale_task_threshold_days_and_more.py +32 -0
  384. trueppm_api/apps/projects/migrations/0104_trash_restore_1113.py +38 -0
  385. trueppm_api/apps/projects/migrations/0105_historical_changelog_indexes.py +62 -0
  386. trueppm_api/apps/projects/migrations/0106_boardsavedview_schema_version.py +20 -0
  387. trueppm_api/apps/projects/migrations/0107_task_activity_event.py +70 -0
  388. trueppm_api/apps/projects/migrations/0108_remove_apitoken_api_token_scope_xor_and_more.py +117 -0
  389. trueppm_api/apps/projects/migrations/0109_projectexportjob.py +82 -0
  390. trueppm_api/apps/projects/migrations/0110_sharelink.py +123 -0
  391. trueppm_api/apps/projects/migrations/0111_projectcalendarlayer.py +67 -0
  392. trueppm_api/apps/projects/migrations/0112_sharelink_expires_at_alter_sharelink_content_kind.py +31 -0
  393. trueppm_api/apps/projects/migrations/0113_alter_historicaltask_percent_complete_and_more.py +35 -0
  394. trueppm_api/apps/projects/migrations/0114_historicalproject_default_member_role_and_more.py +28 -0
  395. trueppm_api/apps/projects/migrations/0115_historicalproject_end_date_shift_threshold_days_and_more.py +22 -0
  396. trueppm_api/apps/projects/migrations/0116_alter_taskactivityevent_event_type.py +28 -0
  397. trueppm_api/apps/projects/migrations/0117_label_tasklabel_label_tasks_and_more.py +126 -0
  398. trueppm_api/apps/projects/migrations/0118_programexportjob.py +82 -0
  399. trueppm_api/apps/projects/migrations/0119_historicalprogram_calendar_program_calendar.py +36 -0
  400. trueppm_api/apps/projects/migrations/0120_alter_backlogitem_item_type.py +29 -0
  401. trueppm_api/apps/projects/migrations/0121_task_three_point_estimate_ordered.py +64 -0
  402. trueppm_api/apps/projects/migrations/0122_task_relation.py +173 -0
  403. trueppm_api/apps/projects/migrations/0123_remove_historicalproject_agile_features_and_more.py +20 -0
  404. trueppm_api/apps/projects/migrations/0124_dependency_is_driving.py +17 -0
  405. trueppm_api/apps/projects/migrations/0125_historicalprogram_estimation_scale_and_more.py +68 -0
  406. trueppm_api/apps/projects/migrations/0126_projectcustomfield_show_on_card_taskcustomfieldvalue.py +80 -0
  407. trueppm_api/apps/projects/migrations/0127_historicalprogram_mcp_enabled_and_more.py +32 -0
  408. trueppm_api/apps/projects/migrations/0128_acceptancecriterion_sync_seq_apitoken_sync_seq_and_more.py +192 -0
  409. trueppm_api/apps/projects/migrations/0129_sharelink_show_milestone_dates.py +20 -0
  410. trueppm_api/apps/projects/migrations/0130_programimportjob.py +85 -0
  411. trueppm_api/apps/projects/migrations/0131_historicalprogram_sprint_picker_ready_only_default_and_more.py +32 -0
  412. trueppm_api/apps/projects/migrations/0132_task_scheduled_start.py +17 -0
  413. trueppm_api/apps/projects/migrations/0133_project_status_date_floor_armed_at.py +17 -0
  414. trueppm_api/apps/projects/migrations/0134_task_edited_at_task_seeded_at_task_source_id_and_more.py +65 -0
  415. trueppm_api/apps/projects/migrations/0135_projecttemplate_templateapplication_and_more.py +175 -0
  416. trueppm_api/apps/projects/migrations/0136_schedule_outline_batch_operations.py +118 -0
  417. trueppm_api/apps/projects/migrations/0137_collapse_dual_scope_api_tokens.py +54 -0
  418. trueppm_api/apps/projects/migrations/0138_apitokenauditentry_api_token_audit_owner_idx.py +18 -0
  419. trueppm_api/apps/projects/migrations/0139_historicaltask_duration_unit_task_duration_unit.py +32 -0
  420. trueppm_api/apps/projects/migrations/0140_historicaltask_auto_container_and_more.py +113 -0
  421. trueppm_api/apps/projects/migrations/0141_historicalproject_draft_started_at_and_more.py +44 -0
  422. trueppm_api/apps/projects/migrations/0142_baseline_calendar_hours_per_day_and_more.py +31 -0
  423. trueppm_api/apps/projects/migrations/0143_sprintcloserequest_failure_reason_and_more.py +42 -0
  424. trueppm_api/apps/projects/migrations/0144_projecttemplate_source_project_and_more.py +35 -0
  425. trueppm_api/apps/projects/migrations/0145_seed_bundled_templates.py +65 -0
  426. trueppm_api/apps/projects/migrations/0146_historicaltask_board_lane_task_board_lane.py +32 -0
  427. trueppm_api/apps/projects/migrations/0147_structuraloperation.py +128 -0
  428. trueppm_api/apps/projects/migrations/0148_task_unique_task_wbs_path_per_project_live.py +76 -0
  429. trueppm_api/apps/projects/migrations/0149_drop_workshops_tables.py +42 -0
  430. trueppm_api/apps/projects/migrations/0150_clear_uncommitted_cpm_output.py +130 -0
  431. trueppm_api/apps/projects/migrations/0151_config_notice_request.py +70 -0
  432. trueppm_api/apps/projects/migrations/0152_aggregate_task_wbs_name_idx.py +20 -0
  433. trueppm_api/apps/projects/migrations/0153_program_sample_anchor_date.py +22 -0
  434. trueppm_api/apps/projects/migrations/__init__.py +0 -0
  435. trueppm_api/apps/projects/models.py +8946 -0
  436. trueppm_api/apps/projects/poker_services.py +154 -0
  437. trueppm_api/apps/projects/poker_views.py +398 -0
  438. trueppm_api/apps/projects/product_backlog_services.py +450 -0
  439. trueppm_api/apps/projects/program_label_services.py +94 -0
  440. trueppm_api/apps/projects/program_label_views.py +189 -0
  441. trueppm_api/apps/projects/program_rollup.py +575 -0
  442. trueppm_api/apps/projects/program_schedule.py +745 -0
  443. trueppm_api/apps/projects/program_views.py +3220 -0
  444. trueppm_api/apps/projects/project_templates.py +422 -0
  445. trueppm_api/apps/projects/receivers.py +234 -0
  446. trueppm_api/apps/projects/refusal_codes.py +140 -0
  447. trueppm_api/apps/projects/reorder_services.py +145 -0
  448. trueppm_api/apps/projects/restructure_hooks.py +54 -0
  449. trueppm_api/apps/projects/retro_board_services.py +368 -0
  450. trueppm_api/apps/projects/retro_services.py +285 -0
  451. trueppm_api/apps/projects/risk_import.py +396 -0
  452. trueppm_api/apps/projects/row_identity.py +113 -0
  453. trueppm_api/apps/projects/schema_migrations.py +228 -0
  454. trueppm_api/apps/projects/schemas/seed_v1.json +493 -0
  455. trueppm_api/apps/projects/schemas/seed_v2.json +892 -0
  456. trueppm_api/apps/projects/seed/__init__.py +61 -0
  457. trueppm_api/apps/projects/seed/exporter.py +1644 -0
  458. trueppm_api/apps/projects/seed/forecast_backfill.py +247 -0
  459. trueppm_api/apps/projects/seed/importer.py +2390 -0
  460. trueppm_api/apps/projects/seed/reanchor.py +551 -0
  461. trueppm_api/apps/projects/seed/reldates.py +126 -0
  462. trueppm_api/apps/projects/seed/replace.py +126 -0
  463. trueppm_api/apps/projects/seed/replay.py +1691 -0
  464. trueppm_api/apps/projects/seed/replay_ctx.py +41 -0
  465. trueppm_api/apps/projects/seed/samples.py +427 -0
  466. trueppm_api/apps/projects/seed/validation.py +1149 -0
  467. trueppm_api/apps/projects/serializers.py +12310 -0
  468. trueppm_api/apps/projects/services.py +6966 -0
  469. trueppm_api/apps/projects/share_serializers.py +132 -0
  470. trueppm_api/apps/projects/share_services.py +325 -0
  471. trueppm_api/apps/projects/share_views.py +374 -0
  472. trueppm_api/apps/projects/sharing_settings.py +187 -0
  473. trueppm_api/apps/projects/signal_privacy_services.py +909 -0
  474. trueppm_api/apps/projects/signal_privacy_views.py +476 -0
  475. trueppm_api/apps/projects/signals.py +132 -0
  476. trueppm_api/apps/projects/slip_conflict.py +266 -0
  477. trueppm_api/apps/projects/sprint_cadence.py +453 -0
  478. trueppm_api/apps/projects/sprint_picker_settings.py +132 -0
  479. trueppm_api/apps/projects/standup.py +304 -0
  480. trueppm_api/apps/projects/standup_views.py +99 -0
  481. trueppm_api/apps/projects/structural_operation_services.py +727 -0
  482. trueppm_api/apps/projects/structural_operation_views.py +311 -0
  483. trueppm_api/apps/projects/surface_visibility.py +126 -0
  484. trueppm_api/apps/projects/task_batch_services.py +379 -0
  485. trueppm_api/apps/projects/task_bulk.py +1152 -0
  486. trueppm_api/apps/projects/task_classification.py +561 -0
  487. trueppm_api/apps/projects/task_classification_defaults.py +95 -0
  488. trueppm_api/apps/projects/task_duration_settings.py +142 -0
  489. trueppm_api/apps/projects/task_grouping.py +754 -0
  490. trueppm_api/apps/projects/tasks.py +1917 -0
  491. trueppm_api/apps/projects/template_divergence.py +168 -0
  492. trueppm_api/apps/projects/template_services.py +160 -0
  493. trueppm_api/apps/projects/template_tasks.py +258 -0
  494. trueppm_api/apps/projects/template_views.py +799 -0
  495. trueppm_api/apps/projects/throttles.py +311 -0
  496. trueppm_api/apps/projects/urls.py +1011 -0
  497. trueppm_api/apps/projects/utilization.py +885 -0
  498. trueppm_api/apps/projects/views.py +20572 -0
  499. trueppm_api/apps/projects/wbs_paths.py +119 -0
  500. trueppm_api/apps/resources/__init__.py +0 -0
  501. trueppm_api/apps/resources/apps.py +8 -0
  502. trueppm_api/apps/resources/capacity.py +121 -0
  503. trueppm_api/apps/resources/migrations/0001_initial.py +68 -0
  504. trueppm_api/apps/resources/migrations/0001_squashed_0013_alter_projectresource_unique_together_and_more.py +281 -0
  505. trueppm_api/apps/resources/migrations/0002_alter_uuid_pk_serialize_false.py +36 -0
  506. trueppm_api/apps/resources/migrations/0003_resource_soft_delete.py +30 -0
  507. trueppm_api/apps/resources/migrations/0004_add_taskresource_resource_index.py +25 -0
  508. trueppm_api/apps/resources/migrations/0005_resource_job_role.py +20 -0
  509. trueppm_api/apps/resources/migrations/0006_skill.py +34 -0
  510. trueppm_api/apps/resources/migrations/0007_resource_skill.py +63 -0
  511. trueppm_api/apps/resources/migrations/0008_project_resource.py +63 -0
  512. trueppm_api/apps/resources/migrations/0009_task_skill_requirement.py +60 -0
  513. trueppm_api/apps/resources/migrations/0010_alter_resourceskill_options_and_more.py +20 -0
  514. trueppm_api/apps/resources/migrations/0011_alter_projectresource_notes.py +17 -0
  515. trueppm_api/apps/resources/migrations/0012_add_resource_user_fk.py +26 -0
  516. trueppm_api/apps/resources/migrations/0013_alter_projectresource_unique_together_and_more.py +53 -0
  517. trueppm_api/apps/resources/migrations/0014_projectresource_sync_seq_resource_sync_seq_and_more.py +37 -0
  518. trueppm_api/apps/resources/migrations/0015_projectresource_deactivated_with_resource.py +17 -0
  519. trueppm_api/apps/resources/migrations/__init__.py +0 -0
  520. trueppm_api/apps/resources/models.py +298 -0
  521. trueppm_api/apps/resources/serializers.py +426 -0
  522. trueppm_api/apps/resources/services.py +465 -0
  523. trueppm_api/apps/resources/urls.py +28 -0
  524. trueppm_api/apps/resources/views.py +1749 -0
  525. trueppm_api/apps/scheduling/__init__.py +0 -0
  526. trueppm_api/apps/scheduling/apps.py +153 -0
  527. trueppm_api/apps/scheduling/calendars.py +119 -0
  528. trueppm_api/apps/scheduling/deadletter.py +91 -0
  529. trueppm_api/apps/scheduling/forecast_history_settings.py +215 -0
  530. trueppm_api/apps/scheduling/forecast_staleness.py +313 -0
  531. trueppm_api/apps/scheduling/graph_guard.py +263 -0
  532. trueppm_api/apps/scheduling/management/__init__.py +0 -0
  533. trueppm_api/apps/scheduling/management/commands/__init__.py +0 -0
  534. trueppm_api/apps/scheduling/management/commands/prune_forecast_snapshots.py +53 -0
  535. trueppm_api/apps/scheduling/migrations/0001_failed_task.py +62 -0
  536. trueppm_api/apps/scheduling/migrations/0001_squashed_0007_projectforecastsnapshot.py +309 -0
  537. trueppm_api/apps/scheduling/migrations/0002_schedulerequest.py +78 -0
  538. trueppm_api/apps/scheduling/migrations/0003_add_schedule_request_reason.py +27 -0
  539. trueppm_api/apps/scheduling/migrations/0004_velocity_suggestion.py +94 -0
  540. trueppm_api/apps/scheduling/migrations/0005_montecarlorun.py +61 -0
  541. trueppm_api/apps/scheduling/migrations/0006_montecarlorun_distribution.py +17 -0
  542. trueppm_api/apps/scheduling/migrations/0007_projectforecastsnapshot.py +65 -0
  543. trueppm_api/apps/scheduling/migrations/0008_alter_schedulerequest_reason.py +28 -0
  544. trueppm_api/apps/scheduling/migrations/0008_folded_schedulerequest_reason.py +34 -0
  545. trueppm_api/apps/scheduling/migrations/0009_alter_schedulerequest_reason.py +29 -0
  546. trueppm_api/apps/scheduling/migrations/0010_failedtask_resolution_note_failedtask_resolved_at_and_more.py +43 -0
  547. trueppm_api/apps/scheduling/migrations/0011_failedtask_project.py +26 -0
  548. trueppm_api/apps/scheduling/migrations/0012_montecarlorun_diagnostic.py +17 -0
  549. trueppm_api/apps/scheduling/migrations/0013_montecarlorun_status_date.py +17 -0
  550. trueppm_api/apps/scheduling/migrations/0014_montecarlorun_plan_version.py +17 -0
  551. trueppm_api/apps/scheduling/migrations/__init__.py +0 -0
  552. trueppm_api/apps/scheduling/models.py +516 -0
  553. trueppm_api/apps/scheduling/receivers.py +160 -0
  554. trueppm_api/apps/scheduling/risk_premium.py +283 -0
  555. trueppm_api/apps/scheduling/serializers.py +564 -0
  556. trueppm_api/apps/scheduling/services.py +1182 -0
  557. trueppm_api/apps/scheduling/signals.py +56 -0
  558. trueppm_api/apps/scheduling/tasks.py +1984 -0
  559. trueppm_api/apps/scheduling/telemetry.py +80 -0
  560. trueppm_api/apps/scheduling/units.py +35 -0
  561. trueppm_api/apps/scheduling/urls.py +57 -0
  562. trueppm_api/apps/scheduling/views.py +2564 -0
  563. trueppm_api/apps/sso/__init__.py +0 -0
  564. trueppm_api/apps/sso/apps.py +19 -0
  565. trueppm_api/apps/sso/extensions.py +130 -0
  566. trueppm_api/apps/sso/management/__init__.py +0 -0
  567. trueppm_api/apps/sso/management/commands/__init__.py +0 -0
  568. trueppm_api/apps/sso/management/commands/seed_sso_keycloak.py +172 -0
  569. trueppm_api/apps/sso/migrations/0001_initial.py +119 -0
  570. trueppm_api/apps/sso/migrations/0002_remove_oidcprovider_workspace_ssoproviderpolicy_and_more.py +169 -0
  571. trueppm_api/apps/sso/migrations/__init__.py +0 -0
  572. trueppm_api/apps/sso/models.py +148 -0
  573. trueppm_api/apps/sso/serializers.py +437 -0
  574. trueppm_api/apps/sso/services.py +1385 -0
  575. trueppm_api/apps/sso/urls.py +47 -0
  576. trueppm_api/apps/sso/views.py +935 -0
  577. trueppm_api/apps/sync/__init__.py +0 -0
  578. trueppm_api/apps/sync/apps.py +15 -0
  579. trueppm_api/apps/sync/backfill.py +208 -0
  580. trueppm_api/apps/sync/broadcast.py +320 -0
  581. trueppm_api/apps/sync/conflict.py +262 -0
  582. trueppm_api/apps/sync/consumers.py +471 -0
  583. trueppm_api/apps/sync/migrations/0001_initial.py +59 -0
  584. trueppm_api/apps/sync/migrations/0001_squashed_0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +77 -0
  585. trueppm_api/apps/sync/migrations/0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +41 -0
  586. trueppm_api/apps/sync/migrations/0003_boardevent.py +46 -0
  587. trueppm_api/apps/sync/migrations/0004_seed_sync_seq.py +24 -0
  588. trueppm_api/apps/sync/migrations/0005_program_sync_sequence.py +35 -0
  589. trueppm_api/apps/sync/migrations/__init__.py +0 -0
  590. trueppm_api/apps/sync/models.py +225 -0
  591. trueppm_api/apps/sync/pagination.py +231 -0
  592. trueppm_api/apps/sync/sequence.py +371 -0
  593. trueppm_api/apps/sync/serializers.py +687 -0
  594. trueppm_api/apps/sync/tasks.py +260 -0
  595. trueppm_api/apps/sync/throttles.py +175 -0
  596. trueppm_api/apps/sync/upload.py +501 -0
  597. trueppm_api/apps/sync/urls.py +17 -0
  598. trueppm_api/apps/sync/views.py +930 -0
  599. trueppm_api/apps/sync/ws_auth.py +193 -0
  600. trueppm_api/apps/taskruns/__init__.py +0 -0
  601. trueppm_api/apps/taskruns/apps.py +11 -0
  602. trueppm_api/apps/taskruns/migrations/0001_initial.py +92 -0
  603. trueppm_api/apps/taskruns/migrations/__init__.py +0 -0
  604. trueppm_api/apps/taskruns/models.py +68 -0
  605. trueppm_api/apps/taskruns/serializers.py +92 -0
  606. trueppm_api/apps/taskruns/tasks.py +60 -0
  607. trueppm_api/apps/taskruns/tracker.py +286 -0
  608. trueppm_api/apps/taskruns/urls.py +46 -0
  609. trueppm_api/apps/taskruns/views.py +195 -0
  610. trueppm_api/apps/teams/__init__.py +8 -0
  611. trueppm_api/apps/teams/apps.py +16 -0
  612. trueppm_api/apps/teams/migrations/0001_initial.py +139 -0
  613. trueppm_api/apps/teams/migrations/0001_squashed_0002_default_teams.py +142 -0
  614. trueppm_api/apps/teams/migrations/0002_default_teams.py +62 -0
  615. trueppm_api/apps/teams/migrations/0003_team_sync_seq_teammembership_sync_seq.py +22 -0
  616. trueppm_api/apps/teams/migrations/__init__.py +0 -0
  617. trueppm_api/apps/teams/models.py +145 -0
  618. trueppm_api/apps/teams/permissions.py +149 -0
  619. trueppm_api/apps/teams/serializers.py +95 -0
  620. trueppm_api/apps/teams/services.py +434 -0
  621. trueppm_api/apps/teams/signals.py +61 -0
  622. trueppm_api/apps/teams/urls.py +34 -0
  623. trueppm_api/apps/teams/views.py +201 -0
  624. trueppm_api/apps/timetracking/__init__.py +0 -0
  625. trueppm_api/apps/timetracking/apps.py +9 -0
  626. trueppm_api/apps/timetracking/migrations/0001_initial.py +117 -0
  627. trueppm_api/apps/timetracking/migrations/0002_timesheetsubmission.py +46 -0
  628. trueppm_api/apps/timetracking/migrations/0003_timeentry_deleted_at_timeentry_deleted_by.py +31 -0
  629. trueppm_api/apps/timetracking/migrations/0004_timeentry_sync_seq_and_more.py +24 -0
  630. trueppm_api/apps/timetracking/migrations/__init__.py +0 -0
  631. trueppm_api/apps/timetracking/models.py +178 -0
  632. trueppm_api/apps/timetracking/serializers.py +175 -0
  633. trueppm_api/apps/timetracking/services.py +117 -0
  634. trueppm_api/apps/timetracking/urls.py +47 -0
  635. trueppm_api/apps/timetracking/views.py +526 -0
  636. trueppm_api/apps/webhooks/__init__.py +0 -0
  637. trueppm_api/apps/webhooks/apps.py +8 -0
  638. trueppm_api/apps/webhooks/backfill.py +90 -0
  639. trueppm_api/apps/webhooks/dispatch.py +144 -0
  640. trueppm_api/apps/webhooks/migrations/0001_initial.py +146 -0
  641. trueppm_api/apps/webhooks/migrations/0001_squashed_0007_sprint_lifecycle_events.py +243 -0
  642. trueppm_api/apps/webhooks/migrations/0002_alter_webhook_events.py +32 -0
  643. trueppm_api/apps/webhooks/migrations/0003_delivery_status_created_idx.py +17 -0
  644. trueppm_api/apps/webhooks/migrations/0004_webhook_program_alter_webhook_project_and_more.py +64 -0
  645. trueppm_api/apps/webhooks/migrations/0005_webhook_delivery_sequence_numbers.py +30 -0
  646. trueppm_api/apps/webhooks/migrations/0006_webhook_format_alter_webhook_events_and_more.py +61 -0
  647. trueppm_api/apps/webhooks/migrations/0007_sprint_lifecycle_events.py +63 -0
  648. trueppm_api/apps/webhooks/migrations/0008_risk_baseline_comment_events.py +73 -0
  649. trueppm_api/apps/webhooks/migrations/0009_encrypt_webhook_secret_and_failure_counters.py +90 -0
  650. trueppm_api/apps/webhooks/migrations/__init__.py +0 -0
  651. trueppm_api/apps/webhooks/models.py +372 -0
  652. trueppm_api/apps/webhooks/serializers.py +318 -0
  653. trueppm_api/apps/webhooks/tasks.py +663 -0
  654. trueppm_api/apps/webhooks/urls.py +22 -0
  655. trueppm_api/apps/webhooks/views.py +383 -0
  656. trueppm_api/apps/workflow_engine/__init__.py +0 -0
  657. trueppm_api/apps/workflow_engine/apps.py +23 -0
  658. trueppm_api/apps/workflow_engine/migrations/0001_initial.py +230 -0
  659. trueppm_api/apps/workflow_engine/migrations/0001_squashed_0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +234 -0
  660. trueppm_api/apps/workflow_engine/migrations/0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +16 -0
  661. trueppm_api/apps/workflow_engine/migrations/0003_workflowoutboxrow_workflow_outbox_recovery_idx.py +21 -0
  662. trueppm_api/apps/workflow_engine/migrations/__init__.py +0 -0
  663. trueppm_api/apps/workflow_engine/models.py +265 -0
  664. trueppm_api/apps/workflow_engine/tasks.py +368 -0
  665. trueppm_api/apps/workspace/__init__.py +0 -0
  666. trueppm_api/apps/workspace/apps.py +10 -0
  667. trueppm_api/apps/workspace/backfill.py +111 -0
  668. trueppm_api/apps/workspace/export.py +242 -0
  669. trueppm_api/apps/workspace/migrations/0001_initial.py +326 -0
  670. trueppm_api/apps/workspace/migrations/0001_squashed_0014_workspacemembership_availability_effective_from_and_more.py +829 -0
  671. trueppm_api/apps/workspace/migrations/0002_remove_workspace_fiscal_year_start_and_more.py +85 -0
  672. trueppm_api/apps/workspace/migrations/0003_workspaceexportjob.py +73 -0
  673. trueppm_api/apps/workspace/migrations/0004_alter_workspaceinvite_status.py +27 -0
  674. trueppm_api/apps/workspace/migrations/0005_workspace_iteration_label_and_more.py +26 -0
  675. trueppm_api/apps/workspace/migrations/0006_alter_groupmembership_unique_together_and_more.py +45 -0
  676. trueppm_api/apps/workspace/migrations/0007_workspace_public_sharing_override_policy.py +21 -0
  677. trueppm_api/apps/workspace/migrations/0008_workspace_mc_history_attribution_audience_and_more.py +44 -0
  678. trueppm_api/apps/workspace/migrations/0009_workspace_methodology_and_more.py +186 -0
  679. trueppm_api/apps/workspace/migrations/0010_workspace_logo.py +38 -0
  680. trueppm_api/apps/workspace/migrations/0011_historicalworkspace_task_duration_change_percent_override_policy_and_more.py +56 -0
  681. trueppm_api/apps/workspace/migrations/0012_historicalworkspace_allowed_attachment_types_and_more.py +64 -0
  682. trueppm_api/apps/workspace/migrations/0013_auditevent.py +70 -0
  683. trueppm_api/apps/workspace/migrations/0014_workspacemembership_availability_effective_from_and_more.py +39 -0
  684. trueppm_api/apps/workspace/migrations/0015_trash_restore_1113.py +34 -0
  685. trueppm_api/apps/workspace/migrations/0016_historicalworkspace_calendar_and_more.py +55 -0
  686. trueppm_api/apps/workspace/migrations/0017_historicalworkspace_estimation_scale_and_more.py +38 -0
  687. trueppm_api/apps/workspace/migrations/0018_historicalworkspace_feedback_enabled_and_more.py +32 -0
  688. trueppm_api/apps/workspace/migrations/0019_historicalworkspace_mcp_enabled_and_more.py +22 -0
  689. trueppm_api/apps/workspace/migrations/0020_viewer_ordinal_one.py +34 -0
  690. trueppm_api/apps/workspace/migrations/0021_group_sync_seq_groupmembership_sync_seq_and_more.py +27 -0
  691. trueppm_api/apps/workspace/migrations/0022_historicalworkspace_sprint_picker_ready_only_default_and_more.py +22 -0
  692. trueppm_api/apps/workspace/migrations/0023_alter_auditevent_event_type.py +32 -0
  693. trueppm_api/apps/workspace/migrations/0024_alter_auditevent_event_type.py +33 -0
  694. trueppm_api/apps/workspace/migrations/0025_alter_auditevent_event_type.py +36 -0
  695. trueppm_api/apps/workspace/migrations/0026_alter_auditevent_event_type.py +37 -0
  696. trueppm_api/apps/workspace/migrations/0027_alter_auditevent_event_type.py +42 -0
  697. trueppm_api/apps/workspace/migrations/__init__.py +0 -0
  698. trueppm_api/apps/workspace/models.py +857 -0
  699. trueppm_api/apps/workspace/permissions.py +187 -0
  700. trueppm_api/apps/workspace/serializers.py +590 -0
  701. trueppm_api/apps/workspace/services.py +919 -0
  702. trueppm_api/apps/workspace/signals.py +37 -0
  703. trueppm_api/apps/workspace/tasks.py +555 -0
  704. trueppm_api/apps/workspace/urls.py +113 -0
  705. trueppm_api/apps/workspace/views.py +1502 -0
  706. trueppm_api/asgi.py +42 -0
  707. trueppm_api/celery.py +31 -0
  708. trueppm_api/core/__init__.py +0 -0
  709. trueppm_api/core/admin_site.py +206 -0
  710. trueppm_api/core/auth_views.py +916 -0
  711. trueppm_api/core/constant_time.py +51 -0
  712. trueppm_api/core/csp.py +54 -0
  713. trueppm_api/core/db.py +87 -0
  714. trueppm_api/core/db_session.py +37 -0
  715. trueppm_api/core/exception_handlers.py +157 -0
  716. trueppm_api/core/export_downloads.py +56 -0
  717. trueppm_api/core/extension_providers.py +56 -0
  718. trueppm_api/core/extension_signals.py +147 -0
  719. trueppm_api/core/idempotent.py +260 -0
  720. trueppm_api/core/metadata.py +42 -0
  721. trueppm_api/core/middleware.py +51 -0
  722. trueppm_api/core/openapi.py +949 -0
  723. trueppm_api/core/password_reset.py +500 -0
  724. trueppm_api/core/protect_conflict.py +100 -0
  725. trueppm_api/core/ratelimit.py +148 -0
  726. trueppm_api/core/redis_throttle.py +82 -0
  727. trueppm_api/core/refresh_cookie_policy.py +156 -0
  728. trueppm_api/core/request_body.py +86 -0
  729. trueppm_api/core/security_checks.py +884 -0
  730. trueppm_api/core/storage_config.py +105 -0
  731. trueppm_api/core/text_decode.py +168 -0
  732. trueppm_api/core/throttling.py +344 -0
  733. trueppm_api/core/valkey.py +214 -0
  734. trueppm_api/core/valkey_cache.py +66 -0
  735. trueppm_api/core/valkey_checks.py +104 -0
  736. trueppm_api/core/valkey_config.py +39 -0
  737. trueppm_api/core/worker_broker_wait.py +149 -0
  738. trueppm_api/core/worker_heartbeat.py +134 -0
  739. trueppm_api/fields.py +36 -0
  740. trueppm_api/permissions.py +30 -0
  741. trueppm_api/py.typed +0 -0
  742. trueppm_api/routing.py +11 -0
  743. trueppm_api/settings/__init__.py +0 -0
  744. trueppm_api/settings/base.py +2455 -0
  745. trueppm_api/settings/dev.py +273 -0
  746. trueppm_api/settings/prod.py +228 -0
  747. trueppm_api/urls.py +153 -0
  748. trueppm_api/workflows/__init__.py +38 -0
  749. trueppm_api/workflows/backends/__init__.py +6 -0
  750. trueppm_api/workflows/backends/default.py +297 -0
  751. trueppm_api/workflows/consumers/__init__.py +11 -0
  752. trueppm_api/workflows/consumers/requeue_failed_task.py +65 -0
  753. trueppm_api/workflows/interface.py +148 -0
  754. trueppm_api/workflows/registry.py +106 -0
  755. trueppm_api/workflows/services.py +90 -0
  756. trueppm_api/wsgi.py +11 -0
  757. trueppm_api-0.4.0b2.dist-info/METADATA +70 -0
  758. trueppm_api-0.4.0b2.dist-info/RECORD +759 -0
  759. trueppm_api-0.4.0b2.dist-info/WHEEL +4 -0
@@ -0,0 +1,313 @@
1
+ """Does this Monte Carlo forecast still describe the current plan? (#3140)
2
+
3
+ A forecast is a statement about a plan at a moment. The plan moves; the forecast does
4
+ not. Every surface that renders a forecast therefore has to answer "is this still
5
+ about the plan I am looking at?" — and until now each one answered it for itself.
6
+
7
+ The web forecast bar answered it with a ``useState(0)`` counter bumped from exactly one
8
+ mutation path, which meant the answer was a fact about one component instance's
9
+ lifetime rather than about the data: it missed inline edits, estimate edits (the actual
10
+ Monte Carlo inputs), a collaborator's write over the WebSocket, and it reset to zero on
11
+ every reload. Once #3132 gated the *Rerun action* on that predicate, the state a user
12
+ most needs the button in — a plan edited before the page was opened — was precisely the
13
+ state that read as "nothing to do".
14
+
15
+ Per ADR-0599 the authoritative classification of a server-computed outcome is itself
16
+ server-computed, so the classification lives here and ships on the payload. No client
17
+ re-derives it.
18
+
19
+ **The plan-version fact is reused, not invented.** ``Project.last_sync_version``
20
+ (ADR-0686 / #2491) is the per-project monotonic sequence every synced row write draws
21
+ its ``sync_seq`` from (:mod:`trueppm_api.apps.sync.sequence`). It is atomic, monotonic
22
+ and authoritative. :mod:`.risk_premium` still carries a comment saying the real stale
23
+ trigger "needs a cheap per-project mutation counter that does not exist yet"; that
24
+ comment predates ADR-0686 and is corrected there.
25
+
26
+ Three properties make it the right counter, and all three are load-bearing:
27
+
28
+ * **It over-approximates, and that is the safe direction.** It bumps for *any* synced
29
+ write in the project — a description edit moves it without moving the plan. So the
30
+ forecast can read stale when it is not. It can never read fresh when it is not, and
31
+ since the defect being fixed is an *unreachable action*, false-stale costs a button
32
+ nobody needed while false-fresh costs the action entirely.
33
+ * **A CPM recompute does not bump it.** CPM output writes go through ``bulk_update``
34
+ (ADR-0091) — no ``save()``, no allocation — so a recalc cannot manufacture staleness
35
+ on a plan nobody touched.
36
+ * **A Monte Carlo run does not bump it.** ``MonteCarloRun`` is a plain ``models.Model``
37
+ outside the sync union, so a run can never make itself stale.
38
+
39
+ **The second property also opens the only false-fresh hole, which is why there is a second
40
+ term.** A run's ``cpm_finish`` is read from *persisted CPM output* (``max(early_finish)``),
41
+ and a recalc rewrites that output without touching the counter. So: an edit bumps the
42
+ counter to N+1 and queues a recalc; the user reruns the forecast before the recalc lands,
43
+ recording ``plan_version = N+1`` against pre-recalc dates; the recalc then lands and moves
44
+ every ``early_finish``. Comparing N+1 to N+1 says ``current`` while the run's
45
+ ``cpm_finish`` — and therefore its ``delta_vs_cpm`` and its whole risk-premium family —
46
+ describes a schedule that no longer exists. The window is not exotic: Rerun sits beside
47
+ the drag that queues the recalc.
48
+
49
+ ``Project.recalculated_at`` (ADR-0114) closes it for free. It is stamped when the recalc
50
+ task succeeds, so a run older than it provably predates the current CPM output, and every
51
+ caller already holds the project row — no extra query.
52
+
53
+ **What is stored where, because the split is not the obvious one.** ``plan_version`` is an
54
+ *observation* made at run time, so it rides both the ``mc_latest`` cache entry and the
55
+ persisted ``MonteCarloRun`` row — it cannot be recovered later from either the project or
56
+ the clock. Only ``plan_version_current`` and the classification itself are derived per
57
+ response. Carrying it on the cache entry is not belt-and-braces: a run triggered by a
58
+ **Member** refreshes the cache but persists no attributed row (#1502), so the cache is the
59
+ *only* place that run's plan version exists.
60
+
61
+ Publishing ``plan_version_current`` is not new disclosure. ``Project.last_sync_version``
62
+ is already returned verbatim to the same Viewer+ audience as the ``timestamp`` cursor of
63
+ every ``GET /api/v1/sync/projects/<pk>/`` response
64
+ (:meth:`trueppm_api.apps.sync.views.ProjectSyncView._watermark`); the field comment on the
65
+ model says "excluded from the sync serializer", which is true of the row serializer and
66
+ easy to misread as "never leaves the server".
67
+
68
+ The audience is not *identical*, though, and the difference should not be lost: the sync
69
+ pull is not an MCP-readable view, while all three surfaces carrying these keys are. So an
70
+ ``mcp:read`` token can now read this counter where none could before. That is a strict
71
+ subset of its owner's own access — the mixin confines tokens to safe methods behind the
72
+ same ``IsProjectMember`` gate — but "no new audience" would be the wrong summary.
73
+
74
+ This module is now a *consumer* of the allocator's monotonicity invariant, whose
75
+ writer-side precondition is tracked in #2617 — a change to the allocator changes what
76
+ "stale" means here.
77
+
78
+ **Composes with, and does not supersede, ``risk_premium_state``.** That enum answers a
79
+ different question — whether the *added-time number* is still worth reporting — and its
80
+ ``stale`` occupies the same slot as ``premium``/``zero``/``negative``, so folding plan
81
+ drift into it would destroy the Overview card's premium classification. This module
82
+ therefore shares :data:`~.risk_premium.STALE_AFTER_DAYS` (one age threshold in the
83
+ codebase, so ``aged`` and ``risk_premium_state == "stale"`` can never disagree about the
84
+ age term) and *adds* the plan-drift term that age alone cannot see. Age is still needed
85
+ on its own: ADR-0132 floors a null status date at today, so an untouched project's
86
+ percentiles genuinely drift with the calendar even though its plan version has not moved.
87
+ """
88
+
89
+ from __future__ import annotations
90
+
91
+ import datetime
92
+ from collections.abc import Mapping
93
+ from typing import Any, Literal, TypedDict
94
+
95
+ from .risk_premium import STALE_AFTER_DAYS
96
+
97
+ #: The forecast-vs-plan discriminant. Never ``None`` on the wire — a client renders from
98
+ #: this value alone and must never have to decide what an absent key means.
99
+ #:
100
+ #: * ``current`` — the run was computed against the version the project is on now, and it
101
+ #: is not old enough for the calendar to have moved underneath it.
102
+ #: * ``project_changed`` — the project has been written to since the run, or its CPM
103
+ #: output has been recomputed since the run.
104
+ #: * ``aged`` — nothing has been written, but the run is older than
105
+ #: :data:`~.risk_premium.STALE_AFTER_DAYS`, so the data date has advanced under it
106
+ #: (ADR-0132) and the percentiles no longer describe the same remaining work.
107
+ #: * ``unknown`` — the run recorded no plan version (it predates #3140), so no comparison
108
+ #: is possible. Deliberately distinct from ``current``: "we cannot tell" is not "it is
109
+ #: fine", and conflating them is how the original defect read.
110
+ #:
111
+ #: **``project_changed``, not ``plan_changed``, and the name is the honest part.** The
112
+ #: counter behind it — ``Project.last_sync_version`` — advances on *any* synced write in
113
+ #: the project, so a description edit or a label moves it without moving a single date.
114
+ #: It is therefore a sound *trigger* ("the inputs may have moved; offer the recompute")
115
+ #: and would be an unsound *claim* ("your plan changed"). A name that overstates its
116
+ #: evidence is worse here than one that understates it, because the value goes to LLM and
117
+ #: MCP callers that will narrate it as fact and have nothing else in the payload to check
118
+ #: it against. Any consumer wanting the stronger claim must corroborate it separately.
119
+ ForecastStaleness = Literal["current", "project_changed", "aged", "unknown"]
120
+
121
+
122
+ class ForecastStalenessFacts(TypedDict):
123
+ """The staleness slice as it appears on a Monte Carlo payload."""
124
+
125
+ forecast_staleness: ForecastStaleness
126
+ #: The project's ``last_sync_version`` when the run was computed. ``None`` for runs
127
+ #: recorded before #3140 (nullable with no backfill, the same discipline
128
+ #: ``distribution``/``diagnostic``/``status_date`` already follow on ``MonteCarloRun``).
129
+ plan_version: int | None
130
+ #: The project's ``last_sync_version`` as of *this response*. Carried beside the run's
131
+ #: own version so a reader — including an MCP client — can state the finding
132
+ #: ("computed against 412, plan is at 419") rather than only its verdict.
133
+ plan_version_current: int | None
134
+
135
+
136
+ def classify_forecast_staleness(
137
+ *,
138
+ plan_version: int | None,
139
+ plan_version_current: int | None,
140
+ age_days: int | None,
141
+ recalculated_after_run: bool = False,
142
+ ) -> ForecastStaleness:
143
+ """Classify one run against the plan it is being read beside.
144
+
145
+ Evaluated in a fixed order, and the order is the safety property: **``current`` is
146
+ unreachable without a recorded plan version.** Every run in every install is
147
+ version-less on the day this ships (nullable, no backfill), and a decision order that
148
+ let one of them fall through to ``current`` because it also happened to be younger
149
+ than the age threshold would hide the Rerun button across the entire fleet — the
150
+ original defect, reintroduced by its own fix. So the missing-input branch is decided
151
+ first, exactly as ``unmeasurable`` is decided before the zero branch in
152
+ :func:`~.risk_premium._premium_state`.
153
+
154
+ Within that branch the age rule still runs, because it answers a *narrower* question
155
+ the timestamp alone can support: a version-less run past
156
+ :data:`~.risk_premium.STALE_AFTER_DAYS` is provably ``aged``, which is strictly more
157
+ useful to a reader than ``unknown`` and equally non-``current``.
158
+
159
+ Args:
160
+ plan_version: The project's plan version when the run was computed, or ``None``
161
+ for a run recorded before #3140.
162
+ plan_version_current: The project's plan version now, or ``None`` when the
163
+ project row could not be read.
164
+ age_days: Whole days between the run and today, or ``None`` when the run carries
165
+ no timestamp.
166
+ recalculated_after_run: Whether the project's CPM output was rewritten after this
167
+ run was taken (``Project.recalculated_at > taken_at``). Closes the one
168
+ false-fresh path the version counter cannot see — see the module docstring.
169
+
170
+ Returns:
171
+ The discriminant a client renders from.
172
+ """
173
+ aged = age_days is not None and age_days > STALE_AFTER_DAYS
174
+
175
+ if plan_version is None or plan_version_current is None:
176
+ # No comparison is possible. Report what the timestamp *can* prove, and otherwise
177
+ # report that the run is unplaceable — never that it is fine. The client keeps the
178
+ # action reachable on both and withholds the stale claim it cannot support.
179
+ if recalculated_after_run:
180
+ return "project_changed"
181
+ return "aged" if aged else "unknown"
182
+
183
+ # Compared with `!=`, not `<`, deliberately: a restored or rolled-back database whose
184
+ # counter went backwards is exactly as unplaceable as one that moved forward, and
185
+ # `<` would call that case `current`.
186
+ if plan_version != plan_version_current or recalculated_after_run:
187
+ return "project_changed"
188
+
189
+ if aged:
190
+ return "aged"
191
+
192
+ return "current"
193
+
194
+
195
+ def forecast_staleness_facts(
196
+ *,
197
+ plan_version: int | None,
198
+ plan_version_current: int | None,
199
+ taken_at: datetime.datetime | None,
200
+ recalculated_at: datetime.datetime | None,
201
+ today: datetime.date,
202
+ ) -> ForecastStalenessFacts:
203
+ """Build the staleness slice for one forecast response.
204
+
205
+ The single entry point — the live run, the cached read and the from-history fallback
206
+ all reach :func:`classify_forecast_staleness` through here, so the platform has one
207
+ derivation of what "stale" means for a forecast.
208
+
209
+ Derived per response and never stored, for the same reason the risk premium is
210
+ (ADR-0698 §2): both of its inputs move underneath a 24-hour cache entry. The plan
211
+ version advances every time anyone writes to the project, and ``today`` advances on
212
+ its own.
213
+ """
214
+ age_days = (today - taken_at.date()).days if taken_at is not None else None
215
+ # Both timestamps are timezone-aware in practice, but a legacy cache entry can carry a
216
+ # naive one; comparing the two would raise TypeError and 500 a read path. Treat an
217
+ # unusable pair as "no evidence of a recalc" rather than letting it decide anything.
218
+ recalculated_after_run = False
219
+ if recalculated_at is not None and taken_at is not None:
220
+ naive = (recalculated_at.tzinfo is None) != (taken_at.tzinfo is None)
221
+ recalculated_after_run = not naive and recalculated_at > taken_at
222
+ return ForecastStalenessFacts(
223
+ forecast_staleness=classify_forecast_staleness(
224
+ plan_version=plan_version,
225
+ plan_version_current=plan_version_current,
226
+ age_days=age_days,
227
+ recalculated_after_run=recalculated_after_run,
228
+ ),
229
+ plan_version=plan_version,
230
+ plan_version_current=plan_version_current,
231
+ )
232
+
233
+
234
+ def forecast_staleness_from_run(
235
+ run: Any | None,
236
+ *,
237
+ plan_version_current: int | None,
238
+ recalculated_at: datetime.datetime | None,
239
+ today: datetime.date,
240
+ ) -> ForecastStalenessFacts:
241
+ """Derive the staleness slice from a persisted :class:`.models.MonteCarloRun`.
242
+
243
+ ``run`` is ``None`` when the project has never been simulated, which classifies as
244
+ ``unknown`` — there is no run to place against a plan. Thin adapter over
245
+ :func:`forecast_staleness_facts`, which owns the derivation, and the counterpart to
246
+ :func:`~.risk_premium.build_risk_premium`.
247
+
248
+ Kept value-shaped rather than request- or response-bound on purpose: an Enterprise
249
+ drift digest (``trueppm-enterprise#192``) has to be able to read this OSS fact by
250
+ importing it — enterprise → core, the one sanctioned direction — instead of
251
+ re-deriving its own answer beside ours and drifting from it.
252
+ """
253
+ if run is None:
254
+ return forecast_staleness_facts(
255
+ plan_version=None,
256
+ plan_version_current=plan_version_current,
257
+ taken_at=None,
258
+ recalculated_at=recalculated_at,
259
+ today=today,
260
+ )
261
+ return forecast_staleness_facts(
262
+ plan_version=run.plan_version,
263
+ plan_version_current=plan_version_current,
264
+ taken_at=run.taken_at,
265
+ recalculated_at=recalculated_at,
266
+ today=today,
267
+ )
268
+
269
+
270
+ def _parse_int(value: Any) -> int | None:
271
+ """Best-effort int; ``None`` for anything that is not one.
272
+
273
+ The cache entry is a plain dict written by a possibly older deploy, so its
274
+ ``plan_version`` is only an int by convention. A ``bool`` is rejected explicitly —
275
+ it is an ``int`` subclass, and letting ``True`` through would compare as ``1`` and
276
+ silently classify a forecast against plan version one.
277
+ """
278
+ if isinstance(value, bool) or not isinstance(value, int):
279
+ return None
280
+ return int(value)
281
+
282
+
283
+ def forecast_staleness_for_payload(
284
+ payload: Mapping[str, Any],
285
+ *,
286
+ plan_version_current: int | None,
287
+ recalculated_at: datetime.datetime | None,
288
+ today: datetime.date,
289
+ ) -> ForecastStalenessFacts:
290
+ """Derive the staleness slice from a cached Monte Carlo forecast dict.
291
+
292
+ Absence is tolerated rather than trusted, mirroring
293
+ :func:`~.risk_premium.risk_premium_for_forecast_payload`: a ``plan_version`` key that
294
+ is missing, ``None`` or not an int resolves to ``unknown`` — "we do not know" —
295
+ instead of inventing a comparison from a partial payload.
296
+ """
297
+ taken_at = payload.get("last_run_at")
298
+ parsed_at: datetime.datetime | None = None
299
+ if isinstance(taken_at, datetime.datetime):
300
+ parsed_at = taken_at
301
+ elif isinstance(taken_at, str):
302
+ try:
303
+ parsed_at = datetime.datetime.fromisoformat(taken_at)
304
+ except ValueError:
305
+ parsed_at = None
306
+
307
+ return forecast_staleness_facts(
308
+ plan_version=_parse_int(payload.get("plan_version")),
309
+ plan_version_current=plan_version_current,
310
+ taken_at=parsed_at,
311
+ recalculated_at=recalculated_at,
312
+ today=today,
313
+ )
@@ -0,0 +1,263 @@
1
+ """Reusable dependency-graph validation for bulk / agent-authored task writes.
2
+
3
+ The human write path (``DependencySerializer._check_no_cycle``) runs
4
+ self-reference and cycle detection on every single proposed edge before it is
5
+ persisted. Bulk and agent write paths — the MS Project importer, the offline
6
+ Jira importer (#1664), the spreadsheet importer, and any future non-interactive
7
+ writer — build whole ``Dependency`` graphs with ``bulk_create`` and therefore
8
+ **bypass** that gate entirely.
9
+
10
+ Every importer named above now calls this guard before its write. The seed
11
+ importer (``apps/seed/importer.py``) still does not — tracked in #2589.
12
+
13
+ That gap is benign only while no bulk writer accepts an untrusted graph. The
14
+ moment a prospect's messy Jira export can be imported (#1664), a cyclic or
15
+ self-referential link would persist an infeasible dependency network that then
16
+ crashes the CPM / what-if engine on the imported data. The differentiator's
17
+ whole claim is that human and agent principals are governed identically; a
18
+ validation gate that only the human path enforces violates "hybrid by
19
+ construction."
20
+
21
+ This module is the shared gate. It wraps the *same* ``trueppm_scheduler``
22
+ ``find_cycle`` algorithm the serializer uses — so the human and agent paths
23
+ detect the identical set of cycles — plus the trivial self-reference identity
24
+ check. Callers pass the *complete* edge set they are about to persist (they
25
+ already hold it in memory, so no DB round-trip is needed) and run this guard
26
+ **before** the write and before ``enqueue_recalculate``.
27
+
28
+ The guard operates on opaque string node ids, so callers may validate in their
29
+ own external-id space (Jira issue keys, MS Project uids) *before* creating any
30
+ row — the cycle/self-reference structure is invariant under relabeling, so a bad
31
+ graph is rejected before a single task is written.
32
+ """
33
+
34
+ from __future__ import annotations
35
+
36
+ import re
37
+ from collections.abc import Mapping
38
+
39
+ from trueppm_scheduler import find_cycle
40
+
41
+ #: Longest one node label may run in a labelled message before it is elided.
42
+ MAX_LABEL_CHARS = 40
43
+ #: Most labels rendered along a cycle path; the rest collapse to "… (N more)".
44
+ #:
45
+ #: A cycle is a *set* of mutually-blocking tasks, so naming three or four of its
46
+ #: members and counting the rest identifies the loop as well as listing all of
47
+ #: them would — and a 200-task cycle must not produce a 200-name sentence.
48
+ MAX_CYCLE_LABELS = 4
49
+ #: Hard ceiling the two caps above are chosen to keep the whole sentence under.
50
+ #:
51
+ #: Not decoration: the classification popover's ``presentable()``
52
+ #: (``useClassificationPopover.ts``) *discards* a server message longer than 300
53
+ #: characters and shows its generic fallback instead, so an unbounded sentence
54
+ #: would silently undo this whole message. Asserted in the guard's tests.
55
+ #:
56
+ #: **Measured in UTF-16 code units, not code points**, because that is what the
57
+ #: check on the other side measures: JavaScript's ``String.length`` counts an
58
+ #: astral character (an emoji in a task name — ordinary, not adversarial) as two.
59
+ #: A code-point budget would pass here and be discarded there, which is the exact
60
+ #: failure this cap exists to prevent.
61
+ MAX_MESSAGE_CHARS = 300
62
+
63
+ #: C0 and C1 control characters, plus DEL.
64
+ _CONTROL_CHARS = re.compile(r"[\x00-\x1f\x7f-\x9f]")
65
+
66
+
67
+ def _utf16_len(text: str) -> int:
68
+ """Length as JavaScript's ``String.length`` would report it."""
69
+ return sum(2 if ord(ch) > 0xFFFF else 1 for ch in text)
70
+
71
+
72
+ def _elide(text: str, limit: int) -> str:
73
+ """Trim ``text`` to at most ``limit`` UTF-16 units, marking the cut with "…".
74
+
75
+ Iterates code points, so the cut can never land between the halves of a
76
+ surrogate pair and produce a lone surrogate.
77
+ """
78
+ if _utf16_len(text) <= limit:
79
+ return text
80
+ kept: list[str] = []
81
+ width = 0
82
+ for ch in text:
83
+ char_width = 2 if ord(ch) > 0xFFFF else 1
84
+ if width + char_width > limit - 1:
85
+ break
86
+ kept.append(ch)
87
+ width += char_width
88
+ return "".join(kept).rstrip() + "…"
89
+
90
+
91
+ def _labelled(node: str, labels: Mapping[str, str]) -> str:
92
+ """A node's human label, falling back to its raw id when it has none.
93
+
94
+ A per-node fallback rather than an all-or-nothing one: a cycle whose members
95
+ are mostly resolvable is still worth naming, and a row deleted between the
96
+ refusal and the lookup must not cost the caller the other names.
97
+
98
+ Control characters are dropped and whitespace collapsed first. A label is
99
+ built from a task name, which is caller-controlled text that ``CharField``
100
+ does not stop from holding a newline, a tab, or an ANSI escape — and this
101
+ string is now *prose*, which a CLI or agent client is far likelier to print
102
+ raw than a JSON field. A name carrying its own line breaks could otherwise
103
+ fake the end of the message and append a sentence of its own.
104
+ """
105
+ printable = _CONTROL_CHARS.sub(" ", labels.get(node, node))
106
+ return _elide(" ".join(printable.split()), MAX_LABEL_CHARS)
107
+
108
+
109
+ def nodes_to_label(offending: list[str]) -> list[str]:
110
+ """The node ids a labelled message will actually name.
111
+
112
+ Exposed so a caller can resolve *only* the ids it is going to print. A cycle
113
+ is capped at :data:`MAX_CYCLE_LABELS` names, so resolving all of a 5000-task
114
+ loop would materialize 5000 rows to render four.
115
+
116
+ Deliberately lives beside :func:`_render_cycle` rather than being reproduced
117
+ at the call site: the two have to agree about which positions survive the
118
+ elision, and a caller re-deriving that slice locally is how they drift apart.
119
+ """
120
+ if len(offending) <= MAX_CYCLE_LABELS:
121
+ return list(offending)
122
+ return [*offending[: MAX_CYCLE_LABELS - 1], offending[-1]]
123
+
124
+
125
+ def _render_cycle(offending: list[str], labels: Mapping[str, str]) -> str:
126
+ """The cycle path as an arrow chain, elided in the middle when it is long.
127
+
128
+ ``find_cycle`` repeats the first node at the end, and that repetition is what
129
+ makes the sentence read as a loop — so the last element is always kept and
130
+ the elision is taken out of the middle.
131
+ """
132
+ if len(offending) <= MAX_CYCLE_LABELS:
133
+ return " → ".join(_labelled(node, labels) for node in offending)
134
+ head = offending[: MAX_CYCLE_LABELS - 1]
135
+ hidden = len(offending) - MAX_CYCLE_LABELS
136
+ parts = [_labelled(node, labels) for node in head]
137
+ parts.append(f"… ({hidden} more)")
138
+ parts.append(_labelled(offending[-1], labels))
139
+ return " → ".join(parts)
140
+
141
+
142
+ def _build_message(reason: str, offending: list[str], labels: Mapping[str, str] | None) -> str:
143
+ """The exception's message, human-facing when the caller supplied labels.
144
+
145
+ Without labels the original domain-signal message is kept verbatim. That is
146
+ what the importers validate in — they run the guard in their own external-id
147
+ space (Jira keys, MS Project uids) *before* any row exists, so there is no
148
+ task to name yet, and each already renders its own sentence from ``reason``
149
+ and ``offending`` (``msproject.importer.describe_bad_graph``).
150
+
151
+ Both branches tolerate an empty ``offending``. Neither is reachable from
152
+ :func:`validate_task_graph`, which always blames at least one node — but the
153
+ message builder must not be the thing that turns a refusal into a 500, or a
154
+ dangling separator, if a later caller constructs one.
155
+ """
156
+ if labels is None:
157
+ return f"Infeasible task graph ({reason}): {offending}"
158
+ if reason == "self_reference":
159
+ node = _labelled(offending[0], labels) if offending else "A task"
160
+ return (
161
+ f"Task {node} lists itself as its own predecessor. "
162
+ "Remove that link to schedule this plan."
163
+ )
164
+ named = _render_cycle(offending, labels) if offending else "these tasks"
165
+ return f"Circular dependency: {named}. Remove one of those links to schedule this plan."
166
+
167
+
168
+ class InfeasibleGraphError(Exception):
169
+ """A proposed task-dependency graph cannot be persisted.
170
+
171
+ Deliberately not a DRF ``ValidationError``: this is a domain signal raised
172
+ from service / importer code that has no request context. Callers translate
173
+ it to whatever their surface needs — an importer marks its outbox row DEAD
174
+ and records the reason on the TaskRunTracker; a view would map it to a 400.
175
+
176
+ Attributes:
177
+ reason: A stable machine code — ``"self_reference"`` or
178
+ ``"cyclic_dependency"`` — so callers can branch (quarantine a
179
+ self-loop vs. reject a whole import) without string-matching the
180
+ message.
181
+ offending: The node ids implicated. For ``"self_reference"`` the single
182
+ offending node; for ``"cyclic_dependency"`` the ordered cycle path
183
+ (first id repeated at the end, e.g. ``["A", "B", "A"]``), matching
184
+ :func:`trueppm_scheduler.find_cycle`.
185
+ labels: ``{node_id: human label}`` when a caller has resolved the ids to
186
+ something the person reading the refusal can find in their plan, or
187
+ ``None`` when the message is still in id space. Only the *message*
188
+ reads this — ``offending`` stays the raw id list either way, because
189
+ that is what a client branches or highlights rows on.
190
+ """
191
+
192
+ def __init__(
193
+ self,
194
+ reason: str,
195
+ offending: list[str],
196
+ *,
197
+ labels: Mapping[str, str] | None = None,
198
+ ) -> None:
199
+ self.reason = reason
200
+ self.offending = offending
201
+ self.labels: dict[str, str] | None = dict(labels) if labels is not None else None
202
+ super().__init__(_build_message(reason, offending, self.labels))
203
+
204
+ def with_labels(self, labels: Mapping[str, str]) -> InfeasibleGraphError:
205
+ """This same refusal, re-worded to name its nodes rather than list their ids.
206
+
207
+ Returns a new instance rather than mutating in place so the raised object
208
+ stays immutable and the original stays available as the ``__cause__`` of
209
+ whatever the caller re-raises.
210
+
211
+ The guard itself operates on opaque node ids and cannot resolve them, so
212
+ labelling belongs to the caller that knows the id space. Doing it *after*
213
+ the refusal rather than passing labels in is deliberate: only the ids the
214
+ guard actually blamed need resolving, and the feasible path — every
215
+ request that is not refused — pays nothing at all.
216
+ """
217
+ return InfeasibleGraphError(self.reason, self.offending, labels=labels)
218
+
219
+
220
+ def validate_task_graph(
221
+ edges: list[tuple[str, str]],
222
+ *,
223
+ children_map: dict[str, list[str]] | None = None,
224
+ ) -> None:
225
+ """Reject a self-referential or cyclic dependency graph before it is written.
226
+
227
+ Runs the same self-reference and :func:`trueppm_scheduler.find_cycle` checks
228
+ the interactive ``DependencySerializer`` path runs, so bulk / agent writers
229
+ are governed identically to the human write path.
230
+
231
+ Args:
232
+ edges: The complete ``(predecessor_id, successor_id)`` set the caller is
233
+ about to persist, as opaque string node ids. Callers that want to
234
+ validate before minting DB rows may pass their external-id space
235
+ (e.g. Jira issue keys) — cycle/self-reference detection is invariant
236
+ under relabeling.
237
+ children_map: Optional ``{summary_id: [child_id, ...]}`` mapping so that
238
+ summary→leaf logical cycles are expanded and caught, matching the
239
+ serializer's summary handling. Omit (``None``) for flat graphs with
240
+ no summary tasks (the minimal-import case), where direct edge cycle
241
+ detection is sufficient.
242
+
243
+ Raises:
244
+ InfeasibleGraphError: With ``reason="self_reference"`` if any edge links
245
+ a task to itself, or ``reason="cyclic_dependency"`` if the graph
246
+ contains a cycle. The message and ``offending`` path let the caller
247
+ surface an actionable error rather than crash the CPM engine.
248
+ InvalidScheduleInput: Re-raised unchanged if the graph is *malformed*
249
+ (a pathological summary fan-out beyond the engine's cap, or a
250
+ children_map that is itself cyclic) — a distinct failure from a
251
+ cycle in the edges. Callers treat it the same as a rejection.
252
+ """
253
+ # Self-reference is a cheap identity check that also gives the caller a
254
+ # precise offending node (find_cycle would report it as a length-1 loop,
255
+ # but a dedicated code lets importers *quarantine* a self-loop — skip it
256
+ # with a warning — rather than reject the whole import).
257
+ for predecessor, successor in edges:
258
+ if predecessor == successor:
259
+ raise InfeasibleGraphError("self_reference", [predecessor])
260
+
261
+ result = find_cycle(edges, children_map=children_map)
262
+ if result.cycle is not None:
263
+ raise InfeasibleGraphError("cyclic_dependency", result.cycle)
File without changes
@@ -0,0 +1,53 @@
1
+ """Apply the tiered retention curve to project forecast snapshots (ADR-0154, #388).
2
+
3
+ Operator-facing wrapper around the same ``_do_prune_forecast_snapshots`` logic the
4
+ nightly ``scheduling.prune_forecast_snapshots`` Celery Beat task runs. Per
5
+ ``settings.FORECAST_SNAPSHOT_RETENTION``: keep all rows younger than ``daily_days``
6
+ (default 90); keep one-per-ISO-week up to ``weekly_days`` (default 365); keep
7
+ one-per-calendar-month beyond that (kept forever).
8
+
9
+ Idempotent: a second run deletes nothing new (the keepers are already the only rows
10
+ left in each bucket).
11
+
12
+ Usage::
13
+
14
+ python manage.py prune_forecast_snapshots # apply
15
+ python manage.py prune_forecast_snapshots --dry-run # report only, no delete
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import logging
21
+ from typing import Any
22
+
23
+ from django.core.management.base import BaseCommand
24
+
25
+ from trueppm_api.apps.scheduling.models import ProjectForecastSnapshot
26
+ from trueppm_api.apps.scheduling.tasks import _do_prune_forecast_snapshots
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+
31
+ class Command(BaseCommand):
32
+ help = "Prune project forecast snapshots per the tiered retention policy (ADR-0154)."
33
+
34
+ def add_arguments(self, parser: Any) -> None:
35
+ parser.add_argument(
36
+ "--dry-run",
37
+ action="store_true",
38
+ help="Report how many rows would be deleted without deleting them.",
39
+ )
40
+
41
+ def handle(self, *args: Any, **options: Any) -> None:
42
+ if options["dry_run"]:
43
+ # Mirror the task's bucketing in a counting-only pass: total - keepers.
44
+ # Re-using the live function would delete, so we report the current count
45
+ # and let the operator run without --dry-run to apply.
46
+ total = ProjectForecastSnapshot.objects.count()
47
+ self.stdout.write(
48
+ f"{total} forecast snapshot(s) on record; run without --dry-run to prune."
49
+ )
50
+ return
51
+
52
+ deleted = _do_prune_forecast_snapshots()
53
+ self.stdout.write(self.style.SUCCESS(f"Pruned {deleted} forecast snapshot(s)."))