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.
- trueppm_api/__init__.py +14 -0
- trueppm_api/apps/access/__init__.py +0 -0
- trueppm_api/apps/access/apps.py +19 -0
- trueppm_api/apps/access/groups.py +518 -0
- trueppm_api/apps/access/management/__init__.py +0 -0
- trueppm_api/apps/access/management/commands/__init__.py +0 -0
- trueppm_api/apps/access/management/commands/create_admin.py +164 -0
- trueppm_api/apps/access/management/commands/migrate_locked.py +96 -0
- trueppm_api/apps/access/management/commands/revoke_api_tokens.py +209 -0
- trueppm_api/apps/access/migrations/0001_initial.py +61 -0
- trueppm_api/apps/access/migrations/0001_squashed_0012_programmembership_role_title.py +167 -0
- trueppm_api/apps/access/migrations/0002_alter_projectmembership_id.py +29 -0
- trueppm_api/apps/access/migrations/0003_projectmembership_soft_delete.py +24 -0
- trueppm_api/apps/access/migrations/0004_alter_projectmembership_role.py +34 -0
- trueppm_api/apps/access/migrations/0005_program_entity_and_membership.py +76 -0
- trueppm_api/apps/access/migrations/0006_role_ordinal_spacing.py +113 -0
- trueppm_api/apps/access/migrations/0007_projectmembership_joined_at_and_more.py +35 -0
- trueppm_api/apps/access/migrations/0008_projectmembership_source_group.py +25 -0
- trueppm_api/apps/access/migrations/0009_projectmembership_pm_proj_serverver_idx.py +20 -0
- trueppm_api/apps/access/migrations/0010_programmembership_joined_at_and_more.py +38 -0
- trueppm_api/apps/access/migrations/0011_alter_programmembership_unique_together_and_more.py +36 -0
- trueppm_api/apps/access/migrations/0012_programmembership_role_title.py +17 -0
- trueppm_api/apps/access/migrations/0013_userdefinedmentiongroup.py +82 -0
- trueppm_api/apps/access/migrations/0014_programmembership_progm_serverver_idx.py +19 -0
- trueppm_api/apps/access/migrations/0015_programuserdefinedmentiongroup.py +86 -0
- trueppm_api/apps/access/migrations/0016_externalstakeholder.py +67 -0
- trueppm_api/apps/access/migrations/0017_viewer_ordinal_one.py +132 -0
- trueppm_api/apps/access/migrations/0018_programmembership_sync_seq_and_more.py +52 -0
- trueppm_api/apps/access/migrations/0019_programmembership_reinstated_at_and_more.py +22 -0
- trueppm_api/apps/access/migrations/__init__.py +0 -0
- trueppm_api/apps/access/models.py +588 -0
- trueppm_api/apps/access/permissions.py +2837 -0
- trueppm_api/apps/access/serializers.py +946 -0
- trueppm_api/apps/access/services.py +667 -0
- trueppm_api/apps/access/signals.py +103 -0
- trueppm_api/apps/access/tasks.py +50 -0
- trueppm_api/apps/access/throttles.py +95 -0
- trueppm_api/apps/access/urls.py +191 -0
- trueppm_api/apps/access/views.py +1931 -0
- trueppm_api/apps/agents/__init__.py +6 -0
- trueppm_api/apps/agents/apps.py +11 -0
- trueppm_api/apps/agents/canonical.py +73 -0
- trueppm_api/apps/agents/deferred.py +108 -0
- trueppm_api/apps/agents/management/__init__.py +0 -0
- trueppm_api/apps/agents/management/commands/__init__.py +0 -0
- trueppm_api/apps/agents/management/commands/audit_prune.py +137 -0
- trueppm_api/apps/agents/management/commands/audit_verify.py +101 -0
- trueppm_api/apps/agents/middleware.py +44 -0
- trueppm_api/apps/agents/migrations/0001_initial.py +231 -0
- trueppm_api/apps/agents/migrations/0002_agentactioncheckpoint.py +71 -0
- trueppm_api/apps/agents/migrations/0003_agentactionrefusaldetail.py +58 -0
- trueppm_api/apps/agents/migrations/__init__.py +0 -0
- trueppm_api/apps/agents/models.py +371 -0
- trueppm_api/apps/agents/refusal.py +103 -0
- trueppm_api/apps/agents/serializers.py +85 -0
- trueppm_api/apps/agents/services.py +397 -0
- trueppm_api/apps/agents/signals.py +30 -0
- trueppm_api/apps/agents/urls.py +15 -0
- trueppm_api/apps/agents/views.py +108 -0
- trueppm_api/apps/csvimport/__init__.py +12 -0
- trueppm_api/apps/csvimport/apps.py +11 -0
- trueppm_api/apps/csvimport/mapping.py +363 -0
- trueppm_api/apps/csvimport/migrations/0001_initial.py +74 -0
- trueppm_api/apps/csvimport/migrations/0002_schedule_outline_batch_operations.py +37 -0
- trueppm_api/apps/csvimport/migrations/0003_csvimportrequest_date_order_and_more.py +26 -0
- trueppm_api/apps/csvimport/migrations/__init__.py +0 -0
- trueppm_api/apps/csvimport/models.py +111 -0
- trueppm_api/apps/csvimport/parser.py +1825 -0
- trueppm_api/apps/csvimport/services.py +48 -0
- trueppm_api/apps/csvimport/tasks.py +418 -0
- trueppm_api/apps/csvimport/template.py +37 -0
- trueppm_api/apps/csvimport/urls.py +50 -0
- trueppm_api/apps/csvimport/views.py +793 -0
- trueppm_api/apps/history/__init__.py +0 -0
- trueppm_api/apps/history/apps.py +12 -0
- trueppm_api/apps/history/changelog.py +359 -0
- trueppm_api/apps/history/diff_policy.py +177 -0
- trueppm_api/apps/history/serializers.py +104 -0
- trueppm_api/apps/history/signals.py +82 -0
- trueppm_api/apps/history/tasks.py +105 -0
- trueppm_api/apps/history/urls.py +38 -0
- trueppm_api/apps/history/views.py +443 -0
- trueppm_api/apps/idempotency/__init__.py +0 -0
- trueppm_api/apps/idempotency/apps.py +11 -0
- trueppm_api/apps/idempotency/migrations/0001_initial.py +71 -0
- trueppm_api/apps/idempotency/migrations/__init__.py +0 -0
- trueppm_api/apps/idempotency/mixins.py +226 -0
- trueppm_api/apps/idempotency/models.py +75 -0
- trueppm_api/apps/idempotency/tasks.py +61 -0
- trueppm_api/apps/integrations/__init__.py +13 -0
- trueppm_api/apps/integrations/apps.py +59 -0
- trueppm_api/apps/integrations/connections.py +806 -0
- trueppm_api/apps/integrations/encryption.py +92 -0
- trueppm_api/apps/integrations/external_sources.py +837 -0
- trueppm_api/apps/integrations/git_automation_services.py +231 -0
- trueppm_api/apps/integrations/git_webhook_auth.py +243 -0
- trueppm_api/apps/integrations/http.py +403 -0
- trueppm_api/apps/integrations/me_work.py +218 -0
- trueppm_api/apps/integrations/migrations/0001_initial.py +64 -0
- trueppm_api/apps/integrations/migrations/0001_squashed_0006_tasklink_description_tasklink_preview_type_and_more.py +187 -0
- trueppm_api/apps/integrations/migrations/0002_tasklink.py +67 -0
- trueppm_api/apps/integrations/migrations/0003_tasklink_tasklink_serverver_idx.py +17 -0
- trueppm_api/apps/integrations/migrations/0004_tasklink_custom_title_tasklink_labels.py +25 -0
- trueppm_api/apps/integrations/migrations/0005_boardautomation.py +56 -0
- trueppm_api/apps/integrations/migrations/0006_tasklink_description_tasklink_preview_type_and_more.py +40 -0
- trueppm_api/apps/integrations/migrations/0007_tasklink_created_at.py +23 -0
- trueppm_api/apps/integrations/migrations/0008_integrationcredential_config_externalworkitem.py +66 -0
- trueppm_api/apps/integrations/migrations/0009_externalsyncrequest_and_more.py +96 -0
- trueppm_api/apps/integrations/migrations/0010_externalworkitem_due_date.py +17 -0
- trueppm_api/apps/integrations/migrations/0011_tasklink_sync_seq_tasklink_tasklink_syncseq_idx.py +22 -0
- trueppm_api/apps/integrations/migrations/0012_boardautomation_last_delivery_at_and_more.py +42 -0
- trueppm_api/apps/integrations/migrations/0013_externalsyncrequest_attempt_count_and_more.py +26 -0
- trueppm_api/apps/integrations/migrations/__init__.py +0 -0
- trueppm_api/apps/integrations/models.py +622 -0
- trueppm_api/apps/integrations/notification_channels.py +57 -0
- trueppm_api/apps/integrations/opengraph.py +229 -0
- trueppm_api/apps/integrations/outgoing.py +201 -0
- trueppm_api/apps/integrations/providers.py +519 -0
- trueppm_api/apps/integrations/registry.py +345 -0
- trueppm_api/apps/integrations/serializers.py +453 -0
- trueppm_api/apps/integrations/services.py +171 -0
- trueppm_api/apps/integrations/tasks.py +731 -0
- trueppm_api/apps/integrations/throttles.py +165 -0
- trueppm_api/apps/integrations/urls.py +77 -0
- trueppm_api/apps/integrations/views.py +1106 -0
- trueppm_api/apps/jiraimport/__init__.py +8 -0
- trueppm_api/apps/jiraimport/apps.py +11 -0
- trueppm_api/apps/jiraimport/migrations/0001_initial.py +72 -0
- trueppm_api/apps/jiraimport/migrations/__init__.py +0 -0
- trueppm_api/apps/jiraimport/models.py +70 -0
- trueppm_api/apps/jiraimport/parser.py +350 -0
- trueppm_api/apps/jiraimport/services.py +46 -0
- trueppm_api/apps/jiraimport/tasks.py +277 -0
- trueppm_api/apps/jiraimport/urls.py +15 -0
- trueppm_api/apps/jiraimport/views.py +153 -0
- trueppm_api/apps/msproject/__init__.py +0 -0
- trueppm_api/apps/msproject/apps.py +11 -0
- trueppm_api/apps/msproject/dataclasses.py +153 -0
- trueppm_api/apps/msproject/exporter.py +500 -0
- trueppm_api/apps/msproject/extended_attributes.py +77 -0
- trueppm_api/apps/msproject/importer.py +1095 -0
- trueppm_api/apps/msproject/migrations/0001_import_request.py +77 -0
- trueppm_api/apps/msproject/migrations/0001_squashed_0002_importrequest_creates_project.py +80 -0
- trueppm_api/apps/msproject/migrations/0002_importrequest_creates_project.py +15 -0
- trueppm_api/apps/msproject/migrations/__init__.py +0 -0
- trueppm_api/apps/msproject/models.py +81 -0
- trueppm_api/apps/msproject/parser.py +1231 -0
- trueppm_api/apps/msproject/serializers.py +136 -0
- trueppm_api/apps/msproject/services.py +58 -0
- trueppm_api/apps/msproject/tasks.py +436 -0
- trueppm_api/apps/msproject/urls.py +39 -0
- trueppm_api/apps/msproject/views.py +531 -0
- trueppm_api/apps/notifications/__init__.py +7 -0
- trueppm_api/apps/notifications/apps.py +15 -0
- trueppm_api/apps/notifications/backfill.py +79 -0
- trueppm_api/apps/notifications/categories.py +112 -0
- trueppm_api/apps/notifications/delivery_limits.py +258 -0
- trueppm_api/apps/notifications/digests.py +447 -0
- trueppm_api/apps/notifications/email_backend.py +330 -0
- trueppm_api/apps/notifications/email_health.py +128 -0
- trueppm_api/apps/notifications/migrations/0001_initial.py +207 -0
- trueppm_api/apps/notifications/migrations/0001_squashed_0006_notification_task.py +294 -0
- trueppm_api/apps/notifications/migrations/0002_project_notification_preference.py +67 -0
- trueppm_api/apps/notifications/migrations/0003_projectnotificationpreference_paused.py +17 -0
- trueppm_api/apps/notifications/migrations/0004_clean_unknown_matrix_keys.py +30 -0
- trueppm_api/apps/notifications/migrations/0005_notification_body_notification_event_type_and_more.py +27 -0
- trueppm_api/apps/notifications/migrations/0006_notification_task.py +25 -0
- trueppm_api/apps/notifications/migrations/0007_workspaceemailsettings.py +80 -0
- trueppm_api/apps/notifications/migrations/0008_notification_snoozed_until.py +17 -0
- trueppm_api/apps/notifications/migrations/0009_usernotificationsettings.py +43 -0
- trueppm_api/apps/notifications/migrations/0010_projectnotificationpreference_schema_version.py +20 -0
- trueppm_api/apps/notifications/migrations/0011_usernotificationsettings_digest_hour_and_more.py +96 -0
- trueppm_api/apps/notifications/migrations/__init__.py +0 -0
- trueppm_api/apps/notifications/models.py +1197 -0
- trueppm_api/apps/notifications/receivers.py +155 -0
- trueppm_api/apps/notifications/schema_migrations.py +56 -0
- trueppm_api/apps/notifications/serializers.py +961 -0
- trueppm_api/apps/notifications/services.py +1451 -0
- trueppm_api/apps/notifications/tasks.py +711 -0
- trueppm_api/apps/notifications/throttles.py +98 -0
- trueppm_api/apps/notifications/urls.py +97 -0
- trueppm_api/apps/notifications/views.py +904 -0
- trueppm_api/apps/observability/__init__.py +0 -0
- trueppm_api/apps/observability/apps.py +31 -0
- trueppm_api/apps/observability/logging.py +213 -0
- trueppm_api/apps/observability/migrations/0001_initial.py +35 -0
- trueppm_api/apps/observability/migrations/0001_squashed_0003_alter_purgerun_trigger.py +154 -0
- trueppm_api/apps/observability/migrations/0002_purgerun_retentionpolicy_retentionschedule.py +131 -0
- trueppm_api/apps/observability/migrations/0003_alter_purgerun_trigger.py +21 -0
- trueppm_api/apps/observability/migrations/0004_alter_retentionpolicy_key.py +28 -0
- trueppm_api/apps/observability/migrations/__init__.py +0 -0
- trueppm_api/apps/observability/models.py +141 -0
- trueppm_api/apps/observability/otel/__init__.py +45 -0
- trueppm_api/apps/observability/otel/attributes.py +247 -0
- trueppm_api/apps/observability/otel/export_health.py +533 -0
- trueppm_api/apps/observability/otel/instrumentation.py +351 -0
- trueppm_api/apps/observability/otel/metrics.py +551 -0
- trueppm_api/apps/observability/otel/provider.py +511 -0
- trueppm_api/apps/observability/otel/request_attributes.py +204 -0
- trueppm_api/apps/observability/purge_registry.py +124 -0
- trueppm_api/apps/observability/retention.py +139 -0
- trueppm_api/apps/observability/selectors.py +969 -0
- trueppm_api/apps/observability/serializers.py +142 -0
- trueppm_api/apps/observability/services.py +407 -0
- trueppm_api/apps/observability/tasks.py +266 -0
- trueppm_api/apps/observability/urls.py +27 -0
- trueppm_api/apps/observability/views.py +523 -0
- trueppm_api/apps/profiles/__init__.py +0 -0
- trueppm_api/apps/profiles/apps.py +15 -0
- trueppm_api/apps/profiles/constants.py +54 -0
- trueppm_api/apps/profiles/migrations/0001_initial.py +55 -0
- trueppm_api/apps/profiles/migrations/0001_squashed_0004_userprofile_role_context.py +125 -0
- trueppm_api/apps/profiles/migrations/0002_userprofile_hidden_views.py +17 -0
- trueppm_api/apps/profiles/migrations/0003_projectvisit.py +63 -0
- trueppm_api/apps/profiles/migrations/0004_userprofile_role_context.py +26 -0
- trueppm_api/apps/profiles/migrations/0005_userprofile_schedule_in_deliver.py +20 -0
- trueppm_api/apps/profiles/migrations/0006_userprofile_date_format_userprofile_timezone.py +36 -0
- trueppm_api/apps/profiles/migrations/0007_userpin.py +91 -0
- trueppm_api/apps/profiles/migrations/0008_remove_userprofile_schedule_in_deliver.py +71 -0
- trueppm_api/apps/profiles/migrations/0009_auth_user_email_upper_idx.py +81 -0
- trueppm_api/apps/profiles/migrations/__init__.py +0 -0
- trueppm_api/apps/profiles/models.py +276 -0
- trueppm_api/apps/profiles/serializers.py +199 -0
- trueppm_api/apps/profiles/services.py +466 -0
- trueppm_api/apps/profiles/views.py +124 -0
- trueppm_api/apps/projects/__init__.py +0 -0
- trueppm_api/apps/projects/actual_date_rules.py +163 -0
- trueppm_api/apps/projects/amend.py +133 -0
- trueppm_api/apps/projects/apps.py +38 -0
- trueppm_api/apps/projects/asset_feed.py +459 -0
- trueppm_api/apps/projects/asset_views.py +317 -0
- trueppm_api/apps/projects/attachment_policy.py +278 -0
- trueppm_api/apps/projects/authentication.py +449 -0
- trueppm_api/apps/projects/backfill.py +287 -0
- trueppm_api/apps/projects/backlog_services.py +324 -0
- trueppm_api/apps/projects/backlog_views.py +302 -0
- trueppm_api/apps/projects/batch_operation_views.py +264 -0
- trueppm_api/apps/projects/blocker_services.py +510 -0
- trueppm_api/apps/projects/board_activity.py +462 -0
- trueppm_api/apps/projects/board_activity_views.py +186 -0
- trueppm_api/apps/projects/board_lanes.py +134 -0
- trueppm_api/apps/projects/bulk_settings.py +194 -0
- trueppm_api/apps/projects/bundled_templates.py +215 -0
- trueppm_api/apps/projects/calendar_settings.py +180 -0
- trueppm_api/apps/projects/ceremony_views.py +244 -0
- trueppm_api/apps/projects/commit_moment.py +204 -0
- trueppm_api/apps/projects/config_notice.py +1325 -0
- trueppm_api/apps/projects/custom_field_values.py +266 -0
- trueppm_api/apps/projects/decisions_services.py +93 -0
- trueppm_api/apps/projects/decisions_views.py +94 -0
- trueppm_api/apps/projects/estimation_scale.py +126 -0
- trueppm_api/apps/projects/export_bundle.py +353 -0
- trueppm_api/apps/projects/fixtures/seeds/atlas-platform-launch.json +4256 -0
- trueppm_api/apps/projects/fixtures/seeds/aurora-mobile-app.json +2000 -0
- trueppm_api/apps/projects/fixtures/seeds/bayside-civic-center.json +1825 -0
- trueppm_api/apps/projects/fixtures/seeds/ga-launch.json +1905 -0
- trueppm_api/apps/projects/fixtures/seeds/helios-crm-replacement.json +1409 -0
- trueppm_api/apps/projects/guardrail_policy_source.py +269 -0
- trueppm_api/apps/projects/inbound_sync.py +555 -0
- trueppm_api/apps/projects/iteration_label.py +85 -0
- trueppm_api/apps/projects/lifecycle.py +191 -0
- trueppm_api/apps/projects/management/__init__.py +0 -0
- trueppm_api/apps/projects/management/commands/__init__.py +0 -0
- trueppm_api/apps/projects/management/commands/backfill_in_progress_status.py +92 -0
- trueppm_api/apps/projects/management/commands/create_demo_share_link.py +368 -0
- trueppm_api/apps/projects/management/commands/export_program.py +50 -0
- trueppm_api/apps/projects/management/commands/import_seed.py +165 -0
- trueppm_api/apps/projects/management/commands/load_sample_project.py +247 -0
- trueppm_api/apps/projects/management/commands/seed_capacity.py +405 -0
- trueppm_api/apps/projects/management/commands/seed_integration_fixtures.py +178 -0
- trueppm_api/apps/projects/management/commands/shift_sample_dates.py +120 -0
- trueppm_api/apps/projects/mcp_settings.py +305 -0
- trueppm_api/apps/projects/methodology.py +178 -0
- trueppm_api/apps/projects/migrations/0001_initial.py +208 -0
- trueppm_api/apps/projects/migrations/0001_squashed_0094_project_decisions_policy.py +5595 -0
- trueppm_api/apps/projects/migrations/0002_alter_uuid_pk_serialize_false.py +57 -0
- trueppm_api/apps/projects/migrations/0003_rename_task_project_index.py +25 -0
- trueppm_api/apps/projects/migrations/0004_soft_delete_and_dependency_versioned.py +70 -0
- trueppm_api/apps/projects/migrations/0005_task_assignee.py +33 -0
- trueppm_api/apps/projects/migrations/0006_task_utilization_index.py +31 -0
- trueppm_api/apps/projects/migrations/0007_task_is_milestone.py +28 -0
- trueppm_api/apps/projects/migrations/0008_baseline.py +119 -0
- trueppm_api/apps/projects/migrations/0009_historicaldependency_historicalproject_and_more.py +210 -0
- trueppm_api/apps/projects/migrations/0010_risk.py +229 -0
- trueppm_api/apps/projects/migrations/0011_task_status.py +27 -0
- trueppm_api/apps/projects/migrations/0012_task_status.py +27 -0
- trueppm_api/apps/projects/migrations/0013_task_planned_start.py +24 -0
- trueppm_api/apps/projects/migrations/0014_short_hex_ids.py +74 -0
- trueppm_api/apps/projects/migrations/0015_backfill_short_ids.py +48 -0
- trueppm_api/apps/projects/migrations/0016_task_actual_dates.py +42 -0
- trueppm_api/apps/projects/migrations/0017_boardcolumnconfig.py +43 -0
- trueppm_api/apps/projects/migrations/0018_estimation_governance.py +62 -0
- trueppm_api/apps/projects/migrations/0019_backfill_wbs_paths.py +28 -0
- trueppm_api/apps/projects/migrations/0020_task_status_5col.py +65 -0
- trueppm_api/apps/projects/migrations/0021_boardsavedview.py +64 -0
- trueppm_api/apps/projects/migrations/0022_task_status_changed_at_priority_rank.py +35 -0
- trueppm_api/apps/projects/migrations/0023_risk_framework_fields.py +96 -0
- trueppm_api/apps/projects/migrations/0024_riskcomment.py +52 -0
- trueppm_api/apps/projects/migrations/0025_sprint.py +352 -0
- trueppm_api/apps/projects/migrations/0026_project_methodology.py +30 -0
- trueppm_api/apps/projects/migrations/0027_sprint_retro.py +89 -0
- trueppm_api/apps/projects/migrations/0028_normalize_notes_field.py +42 -0
- trueppm_api/apps/projects/migrations/0029_complete_implies_full_progress.py +38 -0
- trueppm_api/apps/projects/migrations/0030_review_implies_full_progress.py +37 -0
- trueppm_api/apps/projects/migrations/0031_task_remaining_points.py +22 -0
- trueppm_api/apps/projects/migrations/0032_task_is_subtask_and_sprint_scope_change.py +90 -0
- trueppm_api/apps/projects/migrations/0033_task_my_work_index.py +38 -0
- trueppm_api/apps/projects/migrations/0034_inbound_task_sync.py +313 -0
- trueppm_api/apps/projects/migrations/0035_retro_versioned_and_suggested_assignee.py +173 -0
- trueppm_api/apps/projects/migrations/0036_program_entity_and_membership.py +157 -0
- trueppm_api/apps/projects/migrations/0037_sprint_capacity_points.py +20 -0
- trueppm_api/apps/projects/migrations/0038_taskcomment_commentreaction_commentacknowledgement_and_more.py +233 -0
- trueppm_api/apps/projects/migrations/0039_apitoken_and_more.py +88 -0
- trueppm_api/apps/projects/migrations/0040_program_general_fields.py +119 -0
- trueppm_api/apps/projects/migrations/0041_ceremony_template_and_phase_gate_config.py +210 -0
- trueppm_api/apps/projects/migrations/0041_project_general_fields.py +106 -0
- trueppm_api/apps/projects/migrations/0042_merge_20260522_0410.py +12 -0
- trueppm_api/apps/projects/migrations/0042_program_rollup_config.py +95 -0
- trueppm_api/apps/projects/migrations/0042_workflow_color_and_custom_fields.py +94 -0
- trueppm_api/apps/projects/migrations/0043_merge_20260522_1118.py +10 -0
- trueppm_api/apps/projects/migrations/0044_merge_20260522_rollup.py +10 -0
- trueppm_api/apps/projects/migrations/0044_project_archive_program_close.py +119 -0
- trueppm_api/apps/projects/migrations/0045_program_risk_policy.py +78 -0
- trueppm_api/apps/projects/migrations/0046_merge_20260523_2248.py +12 -0
- trueppm_api/apps/projects/migrations/0047_apitokenauditentry_program_and_more.py +55 -0
- trueppm_api/apps/projects/migrations/0048_historicalprogram_color_program_color.py +32 -0
- trueppm_api/apps/projects/migrations/0049_backlog_item.py +112 -0
- trueppm_api/apps/projects/migrations/0050_backlog_item_trgm_search.py +26 -0
- trueppm_api/apps/projects/migrations/0051_alter_calendar_working_days_alter_dependency_lag_and_more.py +144 -0
- trueppm_api/apps/projects/migrations/0052_recurring_tasks.py +278 -0
- trueppm_api/apps/projects/migrations/0053_dependency_dep_pred_serverver_idx_and_more.py +56 -0
- trueppm_api/apps/projects/migrations/0054_sprintscopechange_goal_impact_and_more.py +117 -0
- trueppm_api/apps/projects/migrations/0055_historicaltask_sprint_pending_and_more.py +51 -0
- trueppm_api/apps/projects/migrations/0056_historicalproject_prioritization_model_and_more.py +276 -0
- trueppm_api/apps/projects/migrations/0057_historicalsprint_binding_committed_snapshot_and_more.py +58 -0
- trueppm_api/apps/projects/migrations/0058_historicaltask_delivery_mode_and_more.py +72 -0
- trueppm_api/apps/projects/migrations/0059_forecastsnapshot.py +80 -0
- trueppm_api/apps/projects/migrations/0060_historicalprojectsignalprivacypolicy_and_more.py +90 -0
- trueppm_api/apps/projects/migrations/0061_baselinetask_story_points.py +17 -0
- trueppm_api/apps/projects/migrations/0062_project_is_sample.py +22 -0
- trueppm_api/apps/projects/migrations/0063_historicalsprint_wip_limit_sprint_wip_limit.py +22 -0
- trueppm_api/apps/projects/migrations/0064_sprinttaskoutcome.py +97 -0
- trueppm_api/apps/projects/migrations/0065_historicalsprint_goal_outcome_sprint_goal_outcome.py +32 -0
- trueppm_api/apps/projects/migrations/0066_historicalproject_lead_project_lead.py +38 -0
- trueppm_api/apps/projects/migrations/0067_sprint_exclude_from_velocity.py +22 -0
- trueppm_api/apps/projects/migrations/0068_project_recalculated_at.py +23 -0
- trueppm_api/apps/projects/migrations/0069_iteration_label.py +22 -0
- trueppm_api/apps/projects/migrations/0070_historicalprogram_iteration_label_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0071_backfill_iteration_label_inherit.py +35 -0
- trueppm_api/apps/projects/migrations/0072_pulseresponse_retroboarditem.py +129 -0
- trueppm_api/apps/projects/migrations/0073_sprinttaskoutcome_demo_ready.py +17 -0
- trueppm_api/apps/projects/migrations/0074_historicaltask_blocked_reason_task_blocked_reason.py +30 -0
- trueppm_api/apps/projects/migrations/0075_risk_decimal_short_id.py +42 -0
- trueppm_api/apps/projects/migrations/0076_sprinttaskoutcome_review_polish.py +45 -0
- trueppm_api/apps/projects/migrations/0077_task_blocker_fields.py +111 -0
- trueppm_api/apps/projects/migrations/0078_alter_risktask_unique_together_and_more.py +22 -0
- trueppm_api/apps/projects/migrations/0079_historicalproject_status_date_project_status_date.py +22 -0
- trueppm_api/apps/projects/migrations/0080_alter_historicaltask_type_alter_task_type.py +46 -0
- trueppm_api/apps/projects/migrations/0081_historicalprogram_allow_guests_and_more.py +52 -0
- trueppm_api/apps/projects/migrations/0082_project_last_sync_version.py +90 -0
- trueppm_api/apps/projects/migrations/0083_historicalprogram_mc_history_attribution_audience_and_more.py +108 -0
- trueppm_api/apps/projects/migrations/0084_tasknote.py +71 -0
- trueppm_api/apps/projects/migrations/0085_sprint_ix_sprint_velocity.py +33 -0
- trueppm_api/apps/projects/migrations/0086_board_card_search_trgm.py +33 -0
- trueppm_api/apps/projects/migrations/0087_historicalprogram_task_duration_change_percent_policy_and_more.py +155 -0
- trueppm_api/apps/projects/migrations/0088_historicalprogram_allowed_attachment_types_and_more.py +61 -0
- trueppm_api/apps/projects/migrations/0089_signalceilingraiseproposal_signalceilingraisevote_and_more.py +157 -0
- trueppm_api/apps/projects/migrations/0090_historicaltask_proj_histdate_index.py +44 -0
- trueppm_api/apps/projects/migrations/0091_project_board_cadence.py +39 -0
- trueppm_api/apps/projects/migrations/0092_program_target_date.py +22 -0
- trueppm_api/apps/projects/migrations/0093_cross_project_dependency_consent.py +58 -0
- trueppm_api/apps/projects/migrations/0094_project_decisions_policy.py +90 -0
- trueppm_api/apps/projects/migrations/0095_poker_session_vote.py +127 -0
- trueppm_api/apps/projects/migrations/0095_squashed_0097_release_0_3.py +225 -0
- trueppm_api/apps/projects/migrations/0096_crossprojectslipconflict.py +91 -0
- trueppm_api/apps/projects/migrations/0097_backlogitem_backlogitem_tags_gin_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0098_historicalproject_show_baselines_and_more.py +52 -0
- trueppm_api/apps/projects/migrations/0099_apitoken_scopes.py +48 -0
- trueppm_api/apps/projects/migrations/0100_task_dep_serverver_idx_concurrent.py +61 -0
- trueppm_api/apps/projects/migrations/0101_dependency_deleted_at_task_deleted_at.py +22 -0
- trueppm_api/apps/projects/migrations/0102_project_deleted_at_project_proj_isdel_deletedat_idx.py +25 -0
- trueppm_api/apps/projects/migrations/0103_taskdurationchangeevent_task_dur_evt_sprint_idx.py +18 -0
- trueppm_api/apps/projects/migrations/0104_historicalproject_stale_task_threshold_days_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0104_trash_restore_1113.py +38 -0
- trueppm_api/apps/projects/migrations/0105_historical_changelog_indexes.py +62 -0
- trueppm_api/apps/projects/migrations/0106_boardsavedview_schema_version.py +20 -0
- trueppm_api/apps/projects/migrations/0107_task_activity_event.py +70 -0
- trueppm_api/apps/projects/migrations/0108_remove_apitoken_api_token_scope_xor_and_more.py +117 -0
- trueppm_api/apps/projects/migrations/0109_projectexportjob.py +82 -0
- trueppm_api/apps/projects/migrations/0110_sharelink.py +123 -0
- trueppm_api/apps/projects/migrations/0111_projectcalendarlayer.py +67 -0
- trueppm_api/apps/projects/migrations/0112_sharelink_expires_at_alter_sharelink_content_kind.py +31 -0
- trueppm_api/apps/projects/migrations/0113_alter_historicaltask_percent_complete_and_more.py +35 -0
- trueppm_api/apps/projects/migrations/0114_historicalproject_default_member_role_and_more.py +28 -0
- trueppm_api/apps/projects/migrations/0115_historicalproject_end_date_shift_threshold_days_and_more.py +22 -0
- trueppm_api/apps/projects/migrations/0116_alter_taskactivityevent_event_type.py +28 -0
- trueppm_api/apps/projects/migrations/0117_label_tasklabel_label_tasks_and_more.py +126 -0
- trueppm_api/apps/projects/migrations/0118_programexportjob.py +82 -0
- trueppm_api/apps/projects/migrations/0119_historicalprogram_calendar_program_calendar.py +36 -0
- trueppm_api/apps/projects/migrations/0120_alter_backlogitem_item_type.py +29 -0
- trueppm_api/apps/projects/migrations/0121_task_three_point_estimate_ordered.py +64 -0
- trueppm_api/apps/projects/migrations/0122_task_relation.py +173 -0
- trueppm_api/apps/projects/migrations/0123_remove_historicalproject_agile_features_and_more.py +20 -0
- trueppm_api/apps/projects/migrations/0124_dependency_is_driving.py +17 -0
- trueppm_api/apps/projects/migrations/0125_historicalprogram_estimation_scale_and_more.py +68 -0
- trueppm_api/apps/projects/migrations/0126_projectcustomfield_show_on_card_taskcustomfieldvalue.py +80 -0
- trueppm_api/apps/projects/migrations/0127_historicalprogram_mcp_enabled_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0128_acceptancecriterion_sync_seq_apitoken_sync_seq_and_more.py +192 -0
- trueppm_api/apps/projects/migrations/0129_sharelink_show_milestone_dates.py +20 -0
- trueppm_api/apps/projects/migrations/0130_programimportjob.py +85 -0
- trueppm_api/apps/projects/migrations/0131_historicalprogram_sprint_picker_ready_only_default_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0132_task_scheduled_start.py +17 -0
- trueppm_api/apps/projects/migrations/0133_project_status_date_floor_armed_at.py +17 -0
- trueppm_api/apps/projects/migrations/0134_task_edited_at_task_seeded_at_task_source_id_and_more.py +65 -0
- trueppm_api/apps/projects/migrations/0135_projecttemplate_templateapplication_and_more.py +175 -0
- trueppm_api/apps/projects/migrations/0136_schedule_outline_batch_operations.py +118 -0
- trueppm_api/apps/projects/migrations/0137_collapse_dual_scope_api_tokens.py +54 -0
- trueppm_api/apps/projects/migrations/0138_apitokenauditentry_api_token_audit_owner_idx.py +18 -0
- trueppm_api/apps/projects/migrations/0139_historicaltask_duration_unit_task_duration_unit.py +32 -0
- trueppm_api/apps/projects/migrations/0140_historicaltask_auto_container_and_more.py +113 -0
- trueppm_api/apps/projects/migrations/0141_historicalproject_draft_started_at_and_more.py +44 -0
- trueppm_api/apps/projects/migrations/0142_baseline_calendar_hours_per_day_and_more.py +31 -0
- trueppm_api/apps/projects/migrations/0143_sprintcloserequest_failure_reason_and_more.py +42 -0
- trueppm_api/apps/projects/migrations/0144_projecttemplate_source_project_and_more.py +35 -0
- trueppm_api/apps/projects/migrations/0145_seed_bundled_templates.py +65 -0
- trueppm_api/apps/projects/migrations/0146_historicaltask_board_lane_task_board_lane.py +32 -0
- trueppm_api/apps/projects/migrations/0147_structuraloperation.py +128 -0
- trueppm_api/apps/projects/migrations/0148_task_unique_task_wbs_path_per_project_live.py +76 -0
- trueppm_api/apps/projects/migrations/0149_drop_workshops_tables.py +42 -0
- trueppm_api/apps/projects/migrations/0150_clear_uncommitted_cpm_output.py +130 -0
- trueppm_api/apps/projects/migrations/0151_config_notice_request.py +70 -0
- trueppm_api/apps/projects/migrations/0152_aggregate_task_wbs_name_idx.py +20 -0
- trueppm_api/apps/projects/migrations/0153_program_sample_anchor_date.py +22 -0
- trueppm_api/apps/projects/migrations/__init__.py +0 -0
- trueppm_api/apps/projects/models.py +8946 -0
- trueppm_api/apps/projects/poker_services.py +154 -0
- trueppm_api/apps/projects/poker_views.py +398 -0
- trueppm_api/apps/projects/product_backlog_services.py +450 -0
- trueppm_api/apps/projects/program_label_services.py +94 -0
- trueppm_api/apps/projects/program_label_views.py +189 -0
- trueppm_api/apps/projects/program_rollup.py +575 -0
- trueppm_api/apps/projects/program_schedule.py +745 -0
- trueppm_api/apps/projects/program_views.py +3220 -0
- trueppm_api/apps/projects/project_templates.py +422 -0
- trueppm_api/apps/projects/receivers.py +234 -0
- trueppm_api/apps/projects/refusal_codes.py +140 -0
- trueppm_api/apps/projects/reorder_services.py +145 -0
- trueppm_api/apps/projects/restructure_hooks.py +54 -0
- trueppm_api/apps/projects/retro_board_services.py +368 -0
- trueppm_api/apps/projects/retro_services.py +285 -0
- trueppm_api/apps/projects/risk_import.py +396 -0
- trueppm_api/apps/projects/row_identity.py +113 -0
- trueppm_api/apps/projects/schema_migrations.py +228 -0
- trueppm_api/apps/projects/schemas/seed_v1.json +493 -0
- trueppm_api/apps/projects/schemas/seed_v2.json +892 -0
- trueppm_api/apps/projects/seed/__init__.py +61 -0
- trueppm_api/apps/projects/seed/exporter.py +1644 -0
- trueppm_api/apps/projects/seed/forecast_backfill.py +247 -0
- trueppm_api/apps/projects/seed/importer.py +2390 -0
- trueppm_api/apps/projects/seed/reanchor.py +551 -0
- trueppm_api/apps/projects/seed/reldates.py +126 -0
- trueppm_api/apps/projects/seed/replace.py +126 -0
- trueppm_api/apps/projects/seed/replay.py +1691 -0
- trueppm_api/apps/projects/seed/replay_ctx.py +41 -0
- trueppm_api/apps/projects/seed/samples.py +427 -0
- trueppm_api/apps/projects/seed/validation.py +1149 -0
- trueppm_api/apps/projects/serializers.py +12310 -0
- trueppm_api/apps/projects/services.py +6966 -0
- trueppm_api/apps/projects/share_serializers.py +132 -0
- trueppm_api/apps/projects/share_services.py +325 -0
- trueppm_api/apps/projects/share_views.py +374 -0
- trueppm_api/apps/projects/sharing_settings.py +187 -0
- trueppm_api/apps/projects/signal_privacy_services.py +909 -0
- trueppm_api/apps/projects/signal_privacy_views.py +476 -0
- trueppm_api/apps/projects/signals.py +132 -0
- trueppm_api/apps/projects/slip_conflict.py +266 -0
- trueppm_api/apps/projects/sprint_cadence.py +453 -0
- trueppm_api/apps/projects/sprint_picker_settings.py +132 -0
- trueppm_api/apps/projects/standup.py +304 -0
- trueppm_api/apps/projects/standup_views.py +99 -0
- trueppm_api/apps/projects/structural_operation_services.py +727 -0
- trueppm_api/apps/projects/structural_operation_views.py +311 -0
- trueppm_api/apps/projects/surface_visibility.py +126 -0
- trueppm_api/apps/projects/task_batch_services.py +379 -0
- trueppm_api/apps/projects/task_bulk.py +1152 -0
- trueppm_api/apps/projects/task_classification.py +561 -0
- trueppm_api/apps/projects/task_classification_defaults.py +95 -0
- trueppm_api/apps/projects/task_duration_settings.py +142 -0
- trueppm_api/apps/projects/task_grouping.py +754 -0
- trueppm_api/apps/projects/tasks.py +1917 -0
- trueppm_api/apps/projects/template_divergence.py +168 -0
- trueppm_api/apps/projects/template_services.py +160 -0
- trueppm_api/apps/projects/template_tasks.py +258 -0
- trueppm_api/apps/projects/template_views.py +799 -0
- trueppm_api/apps/projects/throttles.py +311 -0
- trueppm_api/apps/projects/urls.py +1011 -0
- trueppm_api/apps/projects/utilization.py +885 -0
- trueppm_api/apps/projects/views.py +20572 -0
- trueppm_api/apps/projects/wbs_paths.py +119 -0
- trueppm_api/apps/resources/__init__.py +0 -0
- trueppm_api/apps/resources/apps.py +8 -0
- trueppm_api/apps/resources/capacity.py +121 -0
- trueppm_api/apps/resources/migrations/0001_initial.py +68 -0
- trueppm_api/apps/resources/migrations/0001_squashed_0013_alter_projectresource_unique_together_and_more.py +281 -0
- trueppm_api/apps/resources/migrations/0002_alter_uuid_pk_serialize_false.py +36 -0
- trueppm_api/apps/resources/migrations/0003_resource_soft_delete.py +30 -0
- trueppm_api/apps/resources/migrations/0004_add_taskresource_resource_index.py +25 -0
- trueppm_api/apps/resources/migrations/0005_resource_job_role.py +20 -0
- trueppm_api/apps/resources/migrations/0006_skill.py +34 -0
- trueppm_api/apps/resources/migrations/0007_resource_skill.py +63 -0
- trueppm_api/apps/resources/migrations/0008_project_resource.py +63 -0
- trueppm_api/apps/resources/migrations/0009_task_skill_requirement.py +60 -0
- trueppm_api/apps/resources/migrations/0010_alter_resourceskill_options_and_more.py +20 -0
- trueppm_api/apps/resources/migrations/0011_alter_projectresource_notes.py +17 -0
- trueppm_api/apps/resources/migrations/0012_add_resource_user_fk.py +26 -0
- trueppm_api/apps/resources/migrations/0013_alter_projectresource_unique_together_and_more.py +53 -0
- trueppm_api/apps/resources/migrations/0014_projectresource_sync_seq_resource_sync_seq_and_more.py +37 -0
- trueppm_api/apps/resources/migrations/0015_projectresource_deactivated_with_resource.py +17 -0
- trueppm_api/apps/resources/migrations/__init__.py +0 -0
- trueppm_api/apps/resources/models.py +298 -0
- trueppm_api/apps/resources/serializers.py +426 -0
- trueppm_api/apps/resources/services.py +465 -0
- trueppm_api/apps/resources/urls.py +28 -0
- trueppm_api/apps/resources/views.py +1749 -0
- trueppm_api/apps/scheduling/__init__.py +0 -0
- trueppm_api/apps/scheduling/apps.py +153 -0
- trueppm_api/apps/scheduling/calendars.py +119 -0
- trueppm_api/apps/scheduling/deadletter.py +91 -0
- trueppm_api/apps/scheduling/forecast_history_settings.py +215 -0
- trueppm_api/apps/scheduling/forecast_staleness.py +313 -0
- trueppm_api/apps/scheduling/graph_guard.py +263 -0
- trueppm_api/apps/scheduling/management/__init__.py +0 -0
- trueppm_api/apps/scheduling/management/commands/__init__.py +0 -0
- trueppm_api/apps/scheduling/management/commands/prune_forecast_snapshots.py +53 -0
- trueppm_api/apps/scheduling/migrations/0001_failed_task.py +62 -0
- trueppm_api/apps/scheduling/migrations/0001_squashed_0007_projectforecastsnapshot.py +309 -0
- trueppm_api/apps/scheduling/migrations/0002_schedulerequest.py +78 -0
- trueppm_api/apps/scheduling/migrations/0003_add_schedule_request_reason.py +27 -0
- trueppm_api/apps/scheduling/migrations/0004_velocity_suggestion.py +94 -0
- trueppm_api/apps/scheduling/migrations/0005_montecarlorun.py +61 -0
- trueppm_api/apps/scheduling/migrations/0006_montecarlorun_distribution.py +17 -0
- trueppm_api/apps/scheduling/migrations/0007_projectforecastsnapshot.py +65 -0
- trueppm_api/apps/scheduling/migrations/0008_alter_schedulerequest_reason.py +28 -0
- trueppm_api/apps/scheduling/migrations/0008_folded_schedulerequest_reason.py +34 -0
- trueppm_api/apps/scheduling/migrations/0009_alter_schedulerequest_reason.py +29 -0
- trueppm_api/apps/scheduling/migrations/0010_failedtask_resolution_note_failedtask_resolved_at_and_more.py +43 -0
- trueppm_api/apps/scheduling/migrations/0011_failedtask_project.py +26 -0
- trueppm_api/apps/scheduling/migrations/0012_montecarlorun_diagnostic.py +17 -0
- trueppm_api/apps/scheduling/migrations/0013_montecarlorun_status_date.py +17 -0
- trueppm_api/apps/scheduling/migrations/0014_montecarlorun_plan_version.py +17 -0
- trueppm_api/apps/scheduling/migrations/__init__.py +0 -0
- trueppm_api/apps/scheduling/models.py +516 -0
- trueppm_api/apps/scheduling/receivers.py +160 -0
- trueppm_api/apps/scheduling/risk_premium.py +283 -0
- trueppm_api/apps/scheduling/serializers.py +564 -0
- trueppm_api/apps/scheduling/services.py +1182 -0
- trueppm_api/apps/scheduling/signals.py +56 -0
- trueppm_api/apps/scheduling/tasks.py +1984 -0
- trueppm_api/apps/scheduling/telemetry.py +80 -0
- trueppm_api/apps/scheduling/units.py +35 -0
- trueppm_api/apps/scheduling/urls.py +57 -0
- trueppm_api/apps/scheduling/views.py +2564 -0
- trueppm_api/apps/sso/__init__.py +0 -0
- trueppm_api/apps/sso/apps.py +19 -0
- trueppm_api/apps/sso/extensions.py +130 -0
- trueppm_api/apps/sso/management/__init__.py +0 -0
- trueppm_api/apps/sso/management/commands/__init__.py +0 -0
- trueppm_api/apps/sso/management/commands/seed_sso_keycloak.py +172 -0
- trueppm_api/apps/sso/migrations/0001_initial.py +119 -0
- trueppm_api/apps/sso/migrations/0002_remove_oidcprovider_workspace_ssoproviderpolicy_and_more.py +169 -0
- trueppm_api/apps/sso/migrations/__init__.py +0 -0
- trueppm_api/apps/sso/models.py +148 -0
- trueppm_api/apps/sso/serializers.py +437 -0
- trueppm_api/apps/sso/services.py +1385 -0
- trueppm_api/apps/sso/urls.py +47 -0
- trueppm_api/apps/sso/views.py +935 -0
- trueppm_api/apps/sync/__init__.py +0 -0
- trueppm_api/apps/sync/apps.py +15 -0
- trueppm_api/apps/sync/backfill.py +208 -0
- trueppm_api/apps/sync/broadcast.py +320 -0
- trueppm_api/apps/sync/conflict.py +262 -0
- trueppm_api/apps/sync/consumers.py +471 -0
- trueppm_api/apps/sync/migrations/0001_initial.py +59 -0
- trueppm_api/apps/sync/migrations/0001_squashed_0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +77 -0
- trueppm_api/apps/sync/migrations/0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +41 -0
- trueppm_api/apps/sync/migrations/0003_boardevent.py +46 -0
- trueppm_api/apps/sync/migrations/0004_seed_sync_seq.py +24 -0
- trueppm_api/apps/sync/migrations/0005_program_sync_sequence.py +35 -0
- trueppm_api/apps/sync/migrations/__init__.py +0 -0
- trueppm_api/apps/sync/models.py +225 -0
- trueppm_api/apps/sync/pagination.py +231 -0
- trueppm_api/apps/sync/sequence.py +371 -0
- trueppm_api/apps/sync/serializers.py +687 -0
- trueppm_api/apps/sync/tasks.py +260 -0
- trueppm_api/apps/sync/throttles.py +175 -0
- trueppm_api/apps/sync/upload.py +501 -0
- trueppm_api/apps/sync/urls.py +17 -0
- trueppm_api/apps/sync/views.py +930 -0
- trueppm_api/apps/sync/ws_auth.py +193 -0
- trueppm_api/apps/taskruns/__init__.py +0 -0
- trueppm_api/apps/taskruns/apps.py +11 -0
- trueppm_api/apps/taskruns/migrations/0001_initial.py +92 -0
- trueppm_api/apps/taskruns/migrations/__init__.py +0 -0
- trueppm_api/apps/taskruns/models.py +68 -0
- trueppm_api/apps/taskruns/serializers.py +92 -0
- trueppm_api/apps/taskruns/tasks.py +60 -0
- trueppm_api/apps/taskruns/tracker.py +286 -0
- trueppm_api/apps/taskruns/urls.py +46 -0
- trueppm_api/apps/taskruns/views.py +195 -0
- trueppm_api/apps/teams/__init__.py +8 -0
- trueppm_api/apps/teams/apps.py +16 -0
- trueppm_api/apps/teams/migrations/0001_initial.py +139 -0
- trueppm_api/apps/teams/migrations/0001_squashed_0002_default_teams.py +142 -0
- trueppm_api/apps/teams/migrations/0002_default_teams.py +62 -0
- trueppm_api/apps/teams/migrations/0003_team_sync_seq_teammembership_sync_seq.py +22 -0
- trueppm_api/apps/teams/migrations/__init__.py +0 -0
- trueppm_api/apps/teams/models.py +145 -0
- trueppm_api/apps/teams/permissions.py +149 -0
- trueppm_api/apps/teams/serializers.py +95 -0
- trueppm_api/apps/teams/services.py +434 -0
- trueppm_api/apps/teams/signals.py +61 -0
- trueppm_api/apps/teams/urls.py +34 -0
- trueppm_api/apps/teams/views.py +201 -0
- trueppm_api/apps/timetracking/__init__.py +0 -0
- trueppm_api/apps/timetracking/apps.py +9 -0
- trueppm_api/apps/timetracking/migrations/0001_initial.py +117 -0
- trueppm_api/apps/timetracking/migrations/0002_timesheetsubmission.py +46 -0
- trueppm_api/apps/timetracking/migrations/0003_timeentry_deleted_at_timeentry_deleted_by.py +31 -0
- trueppm_api/apps/timetracking/migrations/0004_timeentry_sync_seq_and_more.py +24 -0
- trueppm_api/apps/timetracking/migrations/__init__.py +0 -0
- trueppm_api/apps/timetracking/models.py +178 -0
- trueppm_api/apps/timetracking/serializers.py +175 -0
- trueppm_api/apps/timetracking/services.py +117 -0
- trueppm_api/apps/timetracking/urls.py +47 -0
- trueppm_api/apps/timetracking/views.py +526 -0
- trueppm_api/apps/webhooks/__init__.py +0 -0
- trueppm_api/apps/webhooks/apps.py +8 -0
- trueppm_api/apps/webhooks/backfill.py +90 -0
- trueppm_api/apps/webhooks/dispatch.py +144 -0
- trueppm_api/apps/webhooks/migrations/0001_initial.py +146 -0
- trueppm_api/apps/webhooks/migrations/0001_squashed_0007_sprint_lifecycle_events.py +243 -0
- trueppm_api/apps/webhooks/migrations/0002_alter_webhook_events.py +32 -0
- trueppm_api/apps/webhooks/migrations/0003_delivery_status_created_idx.py +17 -0
- trueppm_api/apps/webhooks/migrations/0004_webhook_program_alter_webhook_project_and_more.py +64 -0
- trueppm_api/apps/webhooks/migrations/0005_webhook_delivery_sequence_numbers.py +30 -0
- trueppm_api/apps/webhooks/migrations/0006_webhook_format_alter_webhook_events_and_more.py +61 -0
- trueppm_api/apps/webhooks/migrations/0007_sprint_lifecycle_events.py +63 -0
- trueppm_api/apps/webhooks/migrations/0008_risk_baseline_comment_events.py +73 -0
- trueppm_api/apps/webhooks/migrations/0009_encrypt_webhook_secret_and_failure_counters.py +90 -0
- trueppm_api/apps/webhooks/migrations/__init__.py +0 -0
- trueppm_api/apps/webhooks/models.py +372 -0
- trueppm_api/apps/webhooks/serializers.py +318 -0
- trueppm_api/apps/webhooks/tasks.py +663 -0
- trueppm_api/apps/webhooks/urls.py +22 -0
- trueppm_api/apps/webhooks/views.py +383 -0
- trueppm_api/apps/workflow_engine/__init__.py +0 -0
- trueppm_api/apps/workflow_engine/apps.py +23 -0
- trueppm_api/apps/workflow_engine/migrations/0001_initial.py +230 -0
- trueppm_api/apps/workflow_engine/migrations/0001_squashed_0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +234 -0
- trueppm_api/apps/workflow_engine/migrations/0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +16 -0
- trueppm_api/apps/workflow_engine/migrations/0003_workflowoutboxrow_workflow_outbox_recovery_idx.py +21 -0
- trueppm_api/apps/workflow_engine/migrations/__init__.py +0 -0
- trueppm_api/apps/workflow_engine/models.py +265 -0
- trueppm_api/apps/workflow_engine/tasks.py +368 -0
- trueppm_api/apps/workspace/__init__.py +0 -0
- trueppm_api/apps/workspace/apps.py +10 -0
- trueppm_api/apps/workspace/backfill.py +111 -0
- trueppm_api/apps/workspace/export.py +242 -0
- trueppm_api/apps/workspace/migrations/0001_initial.py +326 -0
- trueppm_api/apps/workspace/migrations/0001_squashed_0014_workspacemembership_availability_effective_from_and_more.py +829 -0
- trueppm_api/apps/workspace/migrations/0002_remove_workspace_fiscal_year_start_and_more.py +85 -0
- trueppm_api/apps/workspace/migrations/0003_workspaceexportjob.py +73 -0
- trueppm_api/apps/workspace/migrations/0004_alter_workspaceinvite_status.py +27 -0
- trueppm_api/apps/workspace/migrations/0005_workspace_iteration_label_and_more.py +26 -0
- trueppm_api/apps/workspace/migrations/0006_alter_groupmembership_unique_together_and_more.py +45 -0
- trueppm_api/apps/workspace/migrations/0007_workspace_public_sharing_override_policy.py +21 -0
- trueppm_api/apps/workspace/migrations/0008_workspace_mc_history_attribution_audience_and_more.py +44 -0
- trueppm_api/apps/workspace/migrations/0009_workspace_methodology_and_more.py +186 -0
- trueppm_api/apps/workspace/migrations/0010_workspace_logo.py +38 -0
- trueppm_api/apps/workspace/migrations/0011_historicalworkspace_task_duration_change_percent_override_policy_and_more.py +56 -0
- trueppm_api/apps/workspace/migrations/0012_historicalworkspace_allowed_attachment_types_and_more.py +64 -0
- trueppm_api/apps/workspace/migrations/0013_auditevent.py +70 -0
- trueppm_api/apps/workspace/migrations/0014_workspacemembership_availability_effective_from_and_more.py +39 -0
- trueppm_api/apps/workspace/migrations/0015_trash_restore_1113.py +34 -0
- trueppm_api/apps/workspace/migrations/0016_historicalworkspace_calendar_and_more.py +55 -0
- trueppm_api/apps/workspace/migrations/0017_historicalworkspace_estimation_scale_and_more.py +38 -0
- trueppm_api/apps/workspace/migrations/0018_historicalworkspace_feedback_enabled_and_more.py +32 -0
- trueppm_api/apps/workspace/migrations/0019_historicalworkspace_mcp_enabled_and_more.py +22 -0
- trueppm_api/apps/workspace/migrations/0020_viewer_ordinal_one.py +34 -0
- trueppm_api/apps/workspace/migrations/0021_group_sync_seq_groupmembership_sync_seq_and_more.py +27 -0
- trueppm_api/apps/workspace/migrations/0022_historicalworkspace_sprint_picker_ready_only_default_and_more.py +22 -0
- trueppm_api/apps/workspace/migrations/0023_alter_auditevent_event_type.py +32 -0
- trueppm_api/apps/workspace/migrations/0024_alter_auditevent_event_type.py +33 -0
- trueppm_api/apps/workspace/migrations/0025_alter_auditevent_event_type.py +36 -0
- trueppm_api/apps/workspace/migrations/0026_alter_auditevent_event_type.py +37 -0
- trueppm_api/apps/workspace/migrations/0027_alter_auditevent_event_type.py +42 -0
- trueppm_api/apps/workspace/migrations/__init__.py +0 -0
- trueppm_api/apps/workspace/models.py +857 -0
- trueppm_api/apps/workspace/permissions.py +187 -0
- trueppm_api/apps/workspace/serializers.py +590 -0
- trueppm_api/apps/workspace/services.py +919 -0
- trueppm_api/apps/workspace/signals.py +37 -0
- trueppm_api/apps/workspace/tasks.py +555 -0
- trueppm_api/apps/workspace/urls.py +113 -0
- trueppm_api/apps/workspace/views.py +1502 -0
- trueppm_api/asgi.py +42 -0
- trueppm_api/celery.py +31 -0
- trueppm_api/core/__init__.py +0 -0
- trueppm_api/core/admin_site.py +206 -0
- trueppm_api/core/auth_views.py +916 -0
- trueppm_api/core/constant_time.py +51 -0
- trueppm_api/core/csp.py +54 -0
- trueppm_api/core/db.py +87 -0
- trueppm_api/core/db_session.py +37 -0
- trueppm_api/core/exception_handlers.py +157 -0
- trueppm_api/core/export_downloads.py +56 -0
- trueppm_api/core/extension_providers.py +56 -0
- trueppm_api/core/extension_signals.py +147 -0
- trueppm_api/core/idempotent.py +260 -0
- trueppm_api/core/metadata.py +42 -0
- trueppm_api/core/middleware.py +51 -0
- trueppm_api/core/openapi.py +949 -0
- trueppm_api/core/password_reset.py +500 -0
- trueppm_api/core/protect_conflict.py +100 -0
- trueppm_api/core/ratelimit.py +148 -0
- trueppm_api/core/redis_throttle.py +82 -0
- trueppm_api/core/refresh_cookie_policy.py +156 -0
- trueppm_api/core/request_body.py +86 -0
- trueppm_api/core/security_checks.py +884 -0
- trueppm_api/core/storage_config.py +105 -0
- trueppm_api/core/text_decode.py +168 -0
- trueppm_api/core/throttling.py +344 -0
- trueppm_api/core/valkey.py +214 -0
- trueppm_api/core/valkey_cache.py +66 -0
- trueppm_api/core/valkey_checks.py +104 -0
- trueppm_api/core/valkey_config.py +39 -0
- trueppm_api/core/worker_broker_wait.py +149 -0
- trueppm_api/core/worker_heartbeat.py +134 -0
- trueppm_api/fields.py +36 -0
- trueppm_api/permissions.py +30 -0
- trueppm_api/py.typed +0 -0
- trueppm_api/routing.py +11 -0
- trueppm_api/settings/__init__.py +0 -0
- trueppm_api/settings/base.py +2455 -0
- trueppm_api/settings/dev.py +273 -0
- trueppm_api/settings/prod.py +228 -0
- trueppm_api/urls.py +153 -0
- trueppm_api/workflows/__init__.py +38 -0
- trueppm_api/workflows/backends/__init__.py +6 -0
- trueppm_api/workflows/backends/default.py +297 -0
- trueppm_api/workflows/consumers/__init__.py +11 -0
- trueppm_api/workflows/consumers/requeue_failed_task.py +65 -0
- trueppm_api/workflows/interface.py +148 -0
- trueppm_api/workflows/registry.py +106 -0
- trueppm_api/workflows/services.py +90 -0
- trueppm_api/wsgi.py +11 -0
- trueppm_api-0.4.0b2.dist-info/METADATA +70 -0
- trueppm_api-0.4.0b2.dist-info/RECORD +759 -0
- trueppm_api-0.4.0b2.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Generated by Django 6.0.3 on 2026-05-24 14:21
|
|
2
|
+
|
|
3
|
+
import django.contrib.postgres.fields
|
|
4
|
+
from django.db import migrations, models
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
dependencies = [
|
|
9
|
+
("webhooks", "0005_webhook_delivery_sequence_numbers"),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AddField(
|
|
14
|
+
model_name="webhook",
|
|
15
|
+
name="format",
|
|
16
|
+
field=models.CharField(default="generic", max_length=32),
|
|
17
|
+
),
|
|
18
|
+
migrations.AlterField(
|
|
19
|
+
model_name="webhook",
|
|
20
|
+
name="events",
|
|
21
|
+
field=django.contrib.postgres.fields.ArrayField(
|
|
22
|
+
base_field=models.CharField(
|
|
23
|
+
choices=[
|
|
24
|
+
("task.created", "Task Created"),
|
|
25
|
+
("task.updated", "Task Updated"),
|
|
26
|
+
("task.deleted", "Task Deleted"),
|
|
27
|
+
("dependency.created", "Dependency Created"),
|
|
28
|
+
("dependency.deleted", "Dependency Deleted"),
|
|
29
|
+
("schedule.recalculated", "Schedule Recalculated"),
|
|
30
|
+
("project.created", "Project Created"),
|
|
31
|
+
("task.assigned", "Task Assigned"),
|
|
32
|
+
("task.assignee_changed", "Task Assignee Changed"),
|
|
33
|
+
("task.mentioned", "Task Mentioned"),
|
|
34
|
+
("task.due_date_changed", "Task Due Date Changed"),
|
|
35
|
+
],
|
|
36
|
+
max_length=30,
|
|
37
|
+
),
|
|
38
|
+
help_text="List of event types this webhook subscribes to.",
|
|
39
|
+
),
|
|
40
|
+
),
|
|
41
|
+
migrations.AlterField(
|
|
42
|
+
model_name="webhookdelivery",
|
|
43
|
+
name="event_type",
|
|
44
|
+
field=models.CharField(
|
|
45
|
+
choices=[
|
|
46
|
+
("task.created", "Task Created"),
|
|
47
|
+
("task.updated", "Task Updated"),
|
|
48
|
+
("task.deleted", "Task Deleted"),
|
|
49
|
+
("dependency.created", "Dependency Created"),
|
|
50
|
+
("dependency.deleted", "Dependency Deleted"),
|
|
51
|
+
("schedule.recalculated", "Schedule Recalculated"),
|
|
52
|
+
("project.created", "Project Created"),
|
|
53
|
+
("task.assigned", "Task Assigned"),
|
|
54
|
+
("task.assignee_changed", "Task Assignee Changed"),
|
|
55
|
+
("task.mentioned", "Task Mentioned"),
|
|
56
|
+
("task.due_date_changed", "Task Due Date Changed"),
|
|
57
|
+
],
|
|
58
|
+
max_length=30,
|
|
59
|
+
),
|
|
60
|
+
),
|
|
61
|
+
]
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Generated by Django 5.2.14 on 2026-06-19 19:31
|
|
2
|
+
|
|
3
|
+
import django.contrib.postgres.fields
|
|
4
|
+
from django.db import migrations, models
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
dependencies = [
|
|
9
|
+
("webhooks", "0006_webhook_format_alter_webhook_events_and_more"),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AlterField(
|
|
14
|
+
model_name="webhook",
|
|
15
|
+
name="events",
|
|
16
|
+
field=django.contrib.postgres.fields.ArrayField(
|
|
17
|
+
base_field=models.CharField(
|
|
18
|
+
choices=[
|
|
19
|
+
("task.created", "Task Created"),
|
|
20
|
+
("task.updated", "Task Updated"),
|
|
21
|
+
("task.deleted", "Task Deleted"),
|
|
22
|
+
("dependency.created", "Dependency Created"),
|
|
23
|
+
("dependency.deleted", "Dependency Deleted"),
|
|
24
|
+
("schedule.recalculated", "Schedule Recalculated"),
|
|
25
|
+
("project.created", "Project Created"),
|
|
26
|
+
("task.assigned", "Task Assigned"),
|
|
27
|
+
("task.assignee_changed", "Task Assignee Changed"),
|
|
28
|
+
("task.mentioned", "Task Mentioned"),
|
|
29
|
+
("task.due_date_changed", "Task Due Date Changed"),
|
|
30
|
+
("sprint.activated", "Sprint Activated"),
|
|
31
|
+
("sprint.closed", "Sprint Closed"),
|
|
32
|
+
("sprint.scope_changed", "Sprint Scope Changed"),
|
|
33
|
+
],
|
|
34
|
+
max_length=30,
|
|
35
|
+
),
|
|
36
|
+
help_text="List of event types this webhook subscribes to.",
|
|
37
|
+
size=None,
|
|
38
|
+
),
|
|
39
|
+
),
|
|
40
|
+
migrations.AlterField(
|
|
41
|
+
model_name="webhookdelivery",
|
|
42
|
+
name="event_type",
|
|
43
|
+
field=models.CharField(
|
|
44
|
+
choices=[
|
|
45
|
+
("task.created", "Task Created"),
|
|
46
|
+
("task.updated", "Task Updated"),
|
|
47
|
+
("task.deleted", "Task Deleted"),
|
|
48
|
+
("dependency.created", "Dependency Created"),
|
|
49
|
+
("dependency.deleted", "Dependency Deleted"),
|
|
50
|
+
("schedule.recalculated", "Schedule Recalculated"),
|
|
51
|
+
("project.created", "Project Created"),
|
|
52
|
+
("task.assigned", "Task Assigned"),
|
|
53
|
+
("task.assignee_changed", "Task Assignee Changed"),
|
|
54
|
+
("task.mentioned", "Task Mentioned"),
|
|
55
|
+
("task.due_date_changed", "Task Due Date Changed"),
|
|
56
|
+
("sprint.activated", "Sprint Activated"),
|
|
57
|
+
("sprint.closed", "Sprint Closed"),
|
|
58
|
+
("sprint.scope_changed", "Sprint Scope Changed"),
|
|
59
|
+
],
|
|
60
|
+
max_length=30,
|
|
61
|
+
),
|
|
62
|
+
),
|
|
63
|
+
]
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Generated by Django 5.2.14 on 2026-07-04 18:07
|
|
2
|
+
|
|
3
|
+
import django.contrib.postgres.fields
|
|
4
|
+
from django.db import migrations, models
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
dependencies = [
|
|
9
|
+
("webhooks", "0001_squashed_0007_sprint_lifecycle_events"),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AlterField(
|
|
14
|
+
model_name="webhook",
|
|
15
|
+
name="events",
|
|
16
|
+
field=django.contrib.postgres.fields.ArrayField(
|
|
17
|
+
base_field=models.CharField(
|
|
18
|
+
choices=[
|
|
19
|
+
("task.created", "Task Created"),
|
|
20
|
+
("task.updated", "Task Updated"),
|
|
21
|
+
("task.deleted", "Task Deleted"),
|
|
22
|
+
("dependency.created", "Dependency Created"),
|
|
23
|
+
("dependency.deleted", "Dependency Deleted"),
|
|
24
|
+
("schedule.recalculated", "Schedule Recalculated"),
|
|
25
|
+
("project.created", "Project Created"),
|
|
26
|
+
("task.assigned", "Task Assigned"),
|
|
27
|
+
("task.assignee_changed", "Task Assignee Changed"),
|
|
28
|
+
("task.mentioned", "Task Mentioned"),
|
|
29
|
+
("task.due_date_changed", "Task Due Date Changed"),
|
|
30
|
+
("sprint.activated", "Sprint Activated"),
|
|
31
|
+
("sprint.closed", "Sprint Closed"),
|
|
32
|
+
("sprint.scope_changed", "Sprint Scope Changed"),
|
|
33
|
+
("risk.opened", "Risk Opened"),
|
|
34
|
+
("risk.escalated", "Risk Escalated"),
|
|
35
|
+
("risk.closed", "Risk Closed"),
|
|
36
|
+
("baseline.captured", "Baseline Captured"),
|
|
37
|
+
("comment.created", "Comment Created"),
|
|
38
|
+
],
|
|
39
|
+
max_length=30,
|
|
40
|
+
),
|
|
41
|
+
help_text="List of event types this webhook subscribes to.",
|
|
42
|
+
size=None,
|
|
43
|
+
),
|
|
44
|
+
),
|
|
45
|
+
migrations.AlterField(
|
|
46
|
+
model_name="webhookdelivery",
|
|
47
|
+
name="event_type",
|
|
48
|
+
field=models.CharField(
|
|
49
|
+
choices=[
|
|
50
|
+
("task.created", "Task Created"),
|
|
51
|
+
("task.updated", "Task Updated"),
|
|
52
|
+
("task.deleted", "Task Deleted"),
|
|
53
|
+
("dependency.created", "Dependency Created"),
|
|
54
|
+
("dependency.deleted", "Dependency Deleted"),
|
|
55
|
+
("schedule.recalculated", "Schedule Recalculated"),
|
|
56
|
+
("project.created", "Project Created"),
|
|
57
|
+
("task.assigned", "Task Assigned"),
|
|
58
|
+
("task.assignee_changed", "Task Assignee Changed"),
|
|
59
|
+
("task.mentioned", "Task Mentioned"),
|
|
60
|
+
("task.due_date_changed", "Task Due Date Changed"),
|
|
61
|
+
("sprint.activated", "Sprint Activated"),
|
|
62
|
+
("sprint.closed", "Sprint Closed"),
|
|
63
|
+
("sprint.scope_changed", "Sprint Scope Changed"),
|
|
64
|
+
("risk.opened", "Risk Opened"),
|
|
65
|
+
("risk.escalated", "Risk Escalated"),
|
|
66
|
+
("risk.closed", "Risk Closed"),
|
|
67
|
+
("baseline.captured", "Baseline Captured"),
|
|
68
|
+
("comment.created", "Comment Created"),
|
|
69
|
+
],
|
|
70
|
+
max_length=30,
|
|
71
|
+
),
|
|
72
|
+
),
|
|
73
|
+
]
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Generated by Django 5.2.16 on 2026-08-17 14:09
|
|
2
|
+
#
|
|
3
|
+
# Hand-ordered (#2885): the generated migration emitted RemoveField(secret) first,
|
|
4
|
+
# which would have dropped every signing secret in the install. The AddField must
|
|
5
|
+
# come first, the RunPython copies the plaintext into the new encrypted column
|
|
6
|
+
# while both exist, and only then is the plaintext column dropped. The data step
|
|
7
|
+
# lives in webhooks/backfill.py so a test can exercise it without importing this
|
|
8
|
+
# module by name (CLAUDE.md migration rule 3).
|
|
9
|
+
|
|
10
|
+
from django.db import migrations, models
|
|
11
|
+
|
|
12
|
+
from trueppm_api.apps.webhooks.backfill import (
|
|
13
|
+
encrypt_webhook_secrets,
|
|
14
|
+
reverse_encrypt_webhook_secrets,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Migration(migrations.Migration):
|
|
19
|
+
dependencies = [
|
|
20
|
+
("webhooks", "0008_risk_baseline_comment_events"),
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
operations = [
|
|
24
|
+
migrations.AddField(
|
|
25
|
+
model_name="webhook",
|
|
26
|
+
name="secret_ciphertext",
|
|
27
|
+
field=models.BinaryField(blank=True, default=b""),
|
|
28
|
+
),
|
|
29
|
+
# elidable: a fresh install created from a squashed history has an empty
|
|
30
|
+
# webhooks_webhook table, so this backfill is a guaranteed no-op there.
|
|
31
|
+
# Leaving it non-elidable would make squashmigrations keep it as an
|
|
32
|
+
# optimizer barrier, pinning the add-copy-drop dance for a column no model
|
|
33
|
+
# has — plus a hard import of webhooks.backfill — into the 0.5 baseline.
|
|
34
|
+
migrations.RunPython(
|
|
35
|
+
encrypt_webhook_secrets,
|
|
36
|
+
reverse_encrypt_webhook_secrets,
|
|
37
|
+
elidable=True,
|
|
38
|
+
),
|
|
39
|
+
# State-only, and a no-op forward: `blank`/`default` are not DDL in Django.
|
|
40
|
+
# It exists for the REVERSE direction. RemoveField's backwards pass re-adds
|
|
41
|
+
# the field from the previous state, where `secret` was CharField(max_length=255)
|
|
42
|
+
# with no default and blank=False — so Django emits a bare
|
|
43
|
+
# `ADD COLUMN "secret" varchar(255) NOT NULL`, which PostgreSQL rejects on any
|
|
44
|
+
# table with rows ("contains null values"). That aborts the reverse *before*
|
|
45
|
+
# reverse_encrypt_webhook_secrets can put the plaintext back, on exactly the
|
|
46
|
+
# databases that have webhooks. Declaring blank/default here makes the
|
|
47
|
+
# re-added column carry `DEFAULT ''`. (Passing `field=` to RemoveField would
|
|
48
|
+
# not work: its database_backwards reads the field off to_state.)
|
|
49
|
+
migrations.AlterField(
|
|
50
|
+
model_name="webhook",
|
|
51
|
+
name="secret",
|
|
52
|
+
field=models.CharField(blank=True, default="", max_length=255),
|
|
53
|
+
),
|
|
54
|
+
migrations.RemoveField(
|
|
55
|
+
model_name="webhook",
|
|
56
|
+
name="secret",
|
|
57
|
+
),
|
|
58
|
+
migrations.AddField(
|
|
59
|
+
model_name="webhook",
|
|
60
|
+
name="consecutive_failures",
|
|
61
|
+
field=models.PositiveIntegerField(
|
|
62
|
+
default=0,
|
|
63
|
+
help_text="Terminal delivery failures since the last success. Resets to 0 on any successful delivery.",
|
|
64
|
+
),
|
|
65
|
+
),
|
|
66
|
+
migrations.AddField(
|
|
67
|
+
model_name="webhook",
|
|
68
|
+
name="disabled_at",
|
|
69
|
+
field=models.DateTimeField(
|
|
70
|
+
blank=True,
|
|
71
|
+
help_text="When the automatic failure guard deactivated this subscription. Null when it is active, or when an admin deactivated it by hand.",
|
|
72
|
+
null=True,
|
|
73
|
+
),
|
|
74
|
+
),
|
|
75
|
+
migrations.AddField(
|
|
76
|
+
model_name="webhook",
|
|
77
|
+
name="disabled_reason",
|
|
78
|
+
field=models.CharField(blank=True, default="", max_length=255),
|
|
79
|
+
),
|
|
80
|
+
migrations.AddField(
|
|
81
|
+
model_name="webhook",
|
|
82
|
+
name="last_failure_at",
|
|
83
|
+
field=models.DateTimeField(blank=True, null=True),
|
|
84
|
+
),
|
|
85
|
+
migrations.AddField(
|
|
86
|
+
model_name="webhook",
|
|
87
|
+
name="last_failure_reason",
|
|
88
|
+
field=models.CharField(blank=True, default="", max_length=255),
|
|
89
|
+
),
|
|
90
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
"""Models for outbound webhook subscriptions and delivery tracking."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from django.conf import settings
|
|
9
|
+
from django.contrib.postgres.fields import ArrayField
|
|
10
|
+
from django.db import models, transaction
|
|
11
|
+
from django.utils import timezone
|
|
12
|
+
|
|
13
|
+
from trueppm_api.apps.integrations.encryption import decrypt_secret, encrypt_secret
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class WebhookEventType(models.TextChoices):
|
|
17
|
+
TASK_CREATED = "task.created", "Task Created"
|
|
18
|
+
TASK_UPDATED = "task.updated", "Task Updated"
|
|
19
|
+
TASK_DELETED = "task.deleted", "Task Deleted"
|
|
20
|
+
DEPENDENCY_CREATED = "dependency.created", "Dependency Created"
|
|
21
|
+
DEPENDENCY_DELETED = "dependency.deleted", "Dependency Deleted"
|
|
22
|
+
SCHEDULE_RECALCULATED = "schedule.recalculated", "Schedule Recalculated"
|
|
23
|
+
PROJECT_CREATED = "project.created", "Project Created"
|
|
24
|
+
# Four new task events added in #638 (ADR-0083). task.due_date_changed
|
|
25
|
+
# currently fires on planned_start changes — #690 rebinds it to a dedicated
|
|
26
|
+
# planned_finish deadline field.
|
|
27
|
+
TASK_ASSIGNED = "task.assigned", "Task Assigned"
|
|
28
|
+
TASK_ASSIGNEE_CHANGED = "task.assignee_changed", "Task Assignee Changed"
|
|
29
|
+
TASK_MENTIONED = "task.mentioned", "Task Mentioned"
|
|
30
|
+
TASK_DUE_DATE_CHANGED = "task.due_date_changed", "Task Due Date Changed"
|
|
31
|
+
# Agile trio added in #1073 (ADR-0147). First-party domain events — they make
|
|
32
|
+
# the sprint-sovereignty story (ADR-0102/0104) observable to external tooling.
|
|
33
|
+
# sprint.closed's completion snapshot is velocity and is privacy-gated in its
|
|
34
|
+
# payload builder per ADR-0104 (see _sprint_closed_webhook_payload).
|
|
35
|
+
SPRINT_ACTIVATED = "sprint.activated", "Sprint Activated"
|
|
36
|
+
SPRINT_CLOSED = "sprint.closed", "Sprint Closed"
|
|
37
|
+
SPRINT_SCOPE_CHANGED = "sprint.scope_changed", "Sprint Scope Changed"
|
|
38
|
+
# Risk / baseline / comment domain events added in #1082 (ADR-0206). First-party
|
|
39
|
+
# domain events that extend the catalog beyond agile so external reporting and
|
|
40
|
+
# alerting tooling can observe the risk register, baseline captures, and the
|
|
41
|
+
# comment thread. risk.escalated fires when computed severity (probability ×
|
|
42
|
+
# impact) increases; risk.closed on the transition into CLOSED. comment.created
|
|
43
|
+
# carries no comment body (privacy-conservative) and is distinct from
|
|
44
|
+
# task.mentioned, which fires only when a comment mentions someone.
|
|
45
|
+
RISK_OPENED = "risk.opened", "Risk Opened"
|
|
46
|
+
RISK_ESCALATED = "risk.escalated", "Risk Escalated"
|
|
47
|
+
RISK_CLOSED = "risk.closed", "Risk Closed"
|
|
48
|
+
BASELINE_CAPTURED = "baseline.captured", "Baseline Captured"
|
|
49
|
+
COMMENT_CREATED = "comment.created", "Comment Created"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
ALL_WEBHOOK_EVENTS = [e.value for e in WebhookEventType]
|
|
53
|
+
|
|
54
|
+
# Hard cap on the number of OSS webhook event types (ADR-0083, raised 11→14 by
|
|
55
|
+
# ADR-0147 for the agile trio, then 14→19 by ADR-0206 for the risk/baseline/comment
|
|
56
|
+
# domain events). Adding a 20th event requires its own ADR — this is the gate
|
|
57
|
+
# against the per-customer event proliferation that is the explicit Enterprise
|
|
58
|
+
# upsell. These are all first-party domain events, not custom/user-defined events,
|
|
59
|
+
# so the upsell line is unaffected. ``test_event_type_cap`` fails loudly if
|
|
60
|
+
# WebhookEventType drifts from this number.
|
|
61
|
+
OSS_WEBHOOK_EVENT_CAP = 19
|
|
62
|
+
|
|
63
|
+
# Reserved, non-domain event type used by the "Send test" action (#2884). It is
|
|
64
|
+
# deliberately NOT a WebhookEventType member: no subscription can select it and
|
|
65
|
+
# no domain mutation emits it, but a test ping is a real delivery — it renders
|
|
66
|
+
# through the subscription's provider, allocates a sequence number, and carries
|
|
67
|
+
# the ``_meta`` envelope like any other delivery, so a consumer doing in-body gap
|
|
68
|
+
# detection does not see a phantom jump when an admin clicks Test.
|
|
69
|
+
PING_EVENT_TYPE = "ping"
|
|
70
|
+
|
|
71
|
+
# Consecutive terminal delivery failures after which a subscription is
|
|
72
|
+
# automatically deactivated (#2884). Before this existed, a revoked receiver
|
|
73
|
+
# answering 404/410 was retried forever with no state change: `is_active` was
|
|
74
|
+
# only ever written by a human PATCH, so the queue and the receiver both burned
|
|
75
|
+
# indefinitely. Five is one working day's worth of noise for a low-traffic
|
|
76
|
+
# project and about a minute for a busy one — high enough that a brief receiver
|
|
77
|
+
# outage does not trip it (a transient 5xx recovers within the retry chain and
|
|
78
|
+
# resets the counter on the next success), low enough that a permanently broken
|
|
79
|
+
# endpoint stops being retried.
|
|
80
|
+
AUTO_DISABLE_CONSECUTIVE_FAILURES = 5
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class Webhook(models.Model):
|
|
84
|
+
"""A registered outbound webhook subscription.
|
|
85
|
+
|
|
86
|
+
Polymorphically scoped to either a Project or a Program (ADR-0076).
|
|
87
|
+
A program-scoped webhook fires for events on any project within the
|
|
88
|
+
program; a project-scoped webhook fires only for that project. The DB
|
|
89
|
+
constraint enforces XOR — exactly one of project/program is set.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
93
|
+
project = models.ForeignKey(
|
|
94
|
+
"projects.Project",
|
|
95
|
+
on_delete=models.CASCADE,
|
|
96
|
+
related_name="webhooks",
|
|
97
|
+
null=True,
|
|
98
|
+
blank=True,
|
|
99
|
+
help_text="Set when the webhook fires for events on a single project. "
|
|
100
|
+
"Exactly one of project/program is non-null (DB constraint).",
|
|
101
|
+
)
|
|
102
|
+
program = models.ForeignKey(
|
|
103
|
+
"projects.Program",
|
|
104
|
+
on_delete=models.CASCADE,
|
|
105
|
+
related_name="webhooks",
|
|
106
|
+
null=True,
|
|
107
|
+
blank=True,
|
|
108
|
+
help_text="Set when the webhook fires for events on any project within "
|
|
109
|
+
"this program. Exactly one of project/program is non-null (DB constraint).",
|
|
110
|
+
)
|
|
111
|
+
url = models.URLField(max_length=2048)
|
|
112
|
+
# Fernet ciphertext of the HMAC signing secret (#2885, ADR-0049 §3). This was
|
|
113
|
+
# the last plaintext secret in the product; every sibling secret
|
|
114
|
+
# (IntegrationCredential PAT, inbound git webhook secret, SSO client_secret,
|
|
115
|
+
# workspace SMTP password) already used this at-rest pattern, so a pg_dump or
|
|
116
|
+
# a read-only SQL finding yielded signing keys that let an attacker forge
|
|
117
|
+
# accepted deliveries at every registered receiver.
|
|
118
|
+
#
|
|
119
|
+
# Encrypted rather than hashed: signing needs the plaintext at delivery time
|
|
120
|
+
# (the same reason BoardAutomation.secret_ciphertext is encrypted and
|
|
121
|
+
# ApiToken's is hashed). Read and written through the ``secret`` property
|
|
122
|
+
# below, which keeps ``Webhook.objects.create(secret=...)`` and
|
|
123
|
+
# ``webhook.secret`` working — Django's ``Model.__init__`` routes an unknown
|
|
124
|
+
# kwarg to a same-named property with a setter.
|
|
125
|
+
secret_ciphertext = models.BinaryField(default=b"", blank=True, editable=False)
|
|
126
|
+
events = ArrayField(
|
|
127
|
+
models.CharField(max_length=30, choices=WebhookEventType.choices),
|
|
128
|
+
help_text="List of event types this webhook subscribes to.",
|
|
129
|
+
)
|
|
130
|
+
# Outgoing payload format (#638, ADR-0049/0083). NOT a TextChoices — validated
|
|
131
|
+
# at write time against OUTGOING_CHANNEL_PROVIDERS.keys() in the serializer, so
|
|
132
|
+
# Enterprise can register slack_app/teams without an OSS migration. Existing
|
|
133
|
+
# rows backfill to "generic" (the historical pass-through behavior).
|
|
134
|
+
format = models.CharField(max_length=32, default="generic")
|
|
135
|
+
is_active = models.BooleanField(default=True)
|
|
136
|
+
# Per-subscription delivery-health counters (#2884). Before these existed the
|
|
137
|
+
# only evidence of a failing endpoint was the per-webhook delivery log, which
|
|
138
|
+
# is Admin-only and purges terminal rows after TRUEPPM_WEBHOOK_RETENTION_DAYS
|
|
139
|
+
# (default 7) — so an outage older than a week left no trace anywhere.
|
|
140
|
+
#
|
|
141
|
+
# ``consecutive_failures`` counts *terminal* failures (a delivery that will
|
|
142
|
+
# never be attempted again), not individual attempts, and resets to 0 on the
|
|
143
|
+
# next success. ``disabled_at``/``disabled_reason`` are set only by the
|
|
144
|
+
# automatic guard, never by a human PATCH, so the UI can distinguish "an admin
|
|
145
|
+
# paused this" from "TruePPM gave up on this".
|
|
146
|
+
consecutive_failures = models.PositiveIntegerField(
|
|
147
|
+
default=0,
|
|
148
|
+
help_text="Terminal delivery failures since the last success. Resets to 0 "
|
|
149
|
+
"on any successful delivery.",
|
|
150
|
+
)
|
|
151
|
+
last_failure_at = models.DateTimeField(null=True, blank=True)
|
|
152
|
+
last_failure_reason = models.CharField(max_length=255, blank=True, default="")
|
|
153
|
+
disabled_at = models.DateTimeField(
|
|
154
|
+
null=True,
|
|
155
|
+
blank=True,
|
|
156
|
+
help_text="When the automatic failure guard deactivated this subscription. "
|
|
157
|
+
"Null when it is active, or when an admin deactivated it by hand.",
|
|
158
|
+
)
|
|
159
|
+
disabled_reason = models.CharField(max_length=255, blank=True, default="")
|
|
160
|
+
# Per-subscription monotonic counter for outgoing deliveries (#664). Lives on
|
|
161
|
+
# the subscription, NOT derived from WebhookDelivery rows, because the
|
|
162
|
+
# retention purge (ADR-0081) deletes terminal deliveries — deriving the next
|
|
163
|
+
# value from the delivery table would reuse sequence numbers after a purge
|
|
164
|
+
# and silently corrupt the consumer-side gap-detection contract.
|
|
165
|
+
delivery_sequence = models.BigIntegerField(default=0, editable=False)
|
|
166
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
167
|
+
created_by = models.ForeignKey(
|
|
168
|
+
settings.AUTH_USER_MODEL,
|
|
169
|
+
on_delete=models.SET_NULL,
|
|
170
|
+
null=True,
|
|
171
|
+
blank=True,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
class Meta:
|
|
175
|
+
indexes = [
|
|
176
|
+
models.Index(fields=["project", "is_active"], name="webhook_project_active_idx"),
|
|
177
|
+
models.Index(fields=["program", "is_active"], name="webhook_program_active_idx"),
|
|
178
|
+
]
|
|
179
|
+
constraints = [
|
|
180
|
+
models.CheckConstraint(
|
|
181
|
+
condition=(
|
|
182
|
+
models.Q(project__isnull=False, program__isnull=True)
|
|
183
|
+
| models.Q(project__isnull=True, program__isnull=False)
|
|
184
|
+
),
|
|
185
|
+
name="webhook_scope_xor",
|
|
186
|
+
),
|
|
187
|
+
]
|
|
188
|
+
|
|
189
|
+
def __str__(self) -> str:
|
|
190
|
+
return f"Webhook {self.id} → {self.url}"
|
|
191
|
+
|
|
192
|
+
@property
|
|
193
|
+
def is_program_scoped(self) -> bool:
|
|
194
|
+
"""True when this webhook fires for events on any project in a program."""
|
|
195
|
+
return self.program_id is not None
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
def secret(self) -> str:
|
|
199
|
+
"""The plaintext HMAC signing secret, decrypted from ``secret_ciphertext``.
|
|
200
|
+
|
|
201
|
+
Server-side only — it is never serialized to a client (the API echoes an
|
|
202
|
+
auto-generated secret exactly once at create time and a ``secret_set``
|
|
203
|
+
boolean thereafter). Returns ``""`` when no secret is stored, which the
|
|
204
|
+
delivery task treats as a configuration error rather than signing with an
|
|
205
|
+
empty key.
|
|
206
|
+
"""
|
|
207
|
+
if not self.secret_ciphertext:
|
|
208
|
+
return ""
|
|
209
|
+
return decrypt_secret(self.secret_ciphertext)
|
|
210
|
+
|
|
211
|
+
@secret.setter
|
|
212
|
+
def secret(self, plaintext: str) -> None:
|
|
213
|
+
"""Fernet-encrypt ``plaintext`` into ``secret_ciphertext`` (#2885).
|
|
214
|
+
|
|
215
|
+
A setter rather than an explicit ``set_secret()`` method so every existing
|
|
216
|
+
write path — ``Webhook.objects.create(secret=...)``, DRF's
|
|
217
|
+
``setattr``-based ``ModelSerializer.update``, and fixtures — keeps working
|
|
218
|
+
against the encrypted column with no call-site change. Writing a new value
|
|
219
|
+
is the rotation path: the previous ciphertext is overwritten.
|
|
220
|
+
"""
|
|
221
|
+
self.secret_ciphertext = encrypt_secret(plaintext) if plaintext else b""
|
|
222
|
+
|
|
223
|
+
@property
|
|
224
|
+
def secret_set(self) -> bool:
|
|
225
|
+
"""Whether a signing secret is stored (the only thing the API exposes)."""
|
|
226
|
+
return bool(self.secret_ciphertext)
|
|
227
|
+
|
|
228
|
+
def record_delivery_success(self) -> None:
|
|
229
|
+
"""Clear the failure counters after a delivery succeeds (#2884).
|
|
230
|
+
|
|
231
|
+
Guarded on a non-zero counter so the common case (a healthy webhook
|
|
232
|
+
delivering thousands of events) issues no UPDATE at all. Written with a
|
|
233
|
+
queryset update rather than ``save()`` because the caller holds a delivery
|
|
234
|
+
row, not a lock on the subscription, and two concurrent deliveries to the
|
|
235
|
+
same webhook would otherwise race on a stale in-memory copy.
|
|
236
|
+
"""
|
|
237
|
+
if not (self.consecutive_failures or self.last_failure_at or self.disabled_at):
|
|
238
|
+
return
|
|
239
|
+
Webhook.objects.filter(pk=self.pk).update(
|
|
240
|
+
consecutive_failures=0,
|
|
241
|
+
last_failure_at=None,
|
|
242
|
+
last_failure_reason="",
|
|
243
|
+
)
|
|
244
|
+
self.consecutive_failures = 0
|
|
245
|
+
self.last_failure_at = None
|
|
246
|
+
self.last_failure_reason = ""
|
|
247
|
+
|
|
248
|
+
def record_delivery_failure(self, reason: str) -> bool:
|
|
249
|
+
"""Count one *terminal* delivery failure; auto-disable past the threshold.
|
|
250
|
+
|
|
251
|
+
Called only when a delivery will never be attempted again — a permanent
|
|
252
|
+
client error, an exhausted retry chain, or an SSRF block. Attempts inside a
|
|
253
|
+
live retry chain must not be counted here, or a single event's five
|
|
254
|
+
attempts would trip the guard on their own.
|
|
255
|
+
|
|
256
|
+
The increment uses ``F()`` so concurrent fan-out to the same subscription
|
|
257
|
+
cannot lose a count, then re-reads the committed value to decide whether to
|
|
258
|
+
deactivate. Returns ``True`` when this call deactivated the subscription,
|
|
259
|
+
so the caller can log the transition once.
|
|
260
|
+
"""
|
|
261
|
+
now = timezone.now()
|
|
262
|
+
Webhook.objects.filter(pk=self.pk).update(
|
|
263
|
+
consecutive_failures=models.F("consecutive_failures") + 1,
|
|
264
|
+
last_failure_at=now,
|
|
265
|
+
last_failure_reason=reason[:255],
|
|
266
|
+
)
|
|
267
|
+
failures: int = (
|
|
268
|
+
Webhook.objects.filter(pk=self.pk)
|
|
269
|
+
.values_list("consecutive_failures", flat=True)
|
|
270
|
+
.first()
|
|
271
|
+
or 0
|
|
272
|
+
)
|
|
273
|
+
self.consecutive_failures = failures
|
|
274
|
+
self.last_failure_at = now
|
|
275
|
+
self.last_failure_reason = reason[:255]
|
|
276
|
+
|
|
277
|
+
if failures < AUTO_DISABLE_CONSECUTIVE_FAILURES:
|
|
278
|
+
return False
|
|
279
|
+
# Only the first call across the threshold flips the flag, so a webhook
|
|
280
|
+
# already deactivated (by the guard or by an admin) is not re-stamped.
|
|
281
|
+
disabled_reason = (
|
|
282
|
+
f"Automatically deactivated after {failures} consecutive failed "
|
|
283
|
+
f"deliveries. Last failure: {reason}"
|
|
284
|
+
)[:255]
|
|
285
|
+
updated = Webhook.objects.filter(pk=self.pk, is_active=True).update(
|
|
286
|
+
is_active=False,
|
|
287
|
+
disabled_at=now,
|
|
288
|
+
disabled_reason=disabled_reason,
|
|
289
|
+
)
|
|
290
|
+
if updated:
|
|
291
|
+
self.is_active = False
|
|
292
|
+
self.disabled_at = now
|
|
293
|
+
self.disabled_reason = disabled_reason
|
|
294
|
+
return bool(updated)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def _next_delivery_sequence(webhook_id: uuid.UUID | str) -> int:
|
|
298
|
+
"""Atomically allocate the next per-subscription delivery sequence number.
|
|
299
|
+
|
|
300
|
+
Locks the Webhook row with ``select_for_update`` and increments its
|
|
301
|
+
``delivery_sequence`` counter inside an explicit transaction. The lock is
|
|
302
|
+
held until commit, so concurrent deliveries to the same subscription receive
|
|
303
|
+
strictly increasing, *contiguous* numbers — a gap at the consumer therefore
|
|
304
|
+
signals a genuinely lost event, which is the whole point of the gap-detection
|
|
305
|
+
contract (#664).
|
|
306
|
+
|
|
307
|
+
The explicit ``transaction.atomic()`` is required because ``dispatch_webhooks``
|
|
308
|
+
runs from a ``transaction.on_commit`` callback (autocommit context); without
|
|
309
|
+
the surrounding transaction the increment + read-back would race.
|
|
310
|
+
"""
|
|
311
|
+
with transaction.atomic():
|
|
312
|
+
webhook = Webhook.objects.select_for_update().get(pk=webhook_id)
|
|
313
|
+
webhook.delivery_sequence += 1
|
|
314
|
+
webhook.save(update_fields=["delivery_sequence"])
|
|
315
|
+
return webhook.delivery_sequence
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
class DeliveryStatus(models.TextChoices):
|
|
319
|
+
PENDING = "pending", "Pending"
|
|
320
|
+
SUCCESS = "success", "Success"
|
|
321
|
+
FAILED = "failed", "Failed"
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
class WebhookDelivery(models.Model):
|
|
325
|
+
"""Record of a single webhook delivery attempt (or series of retries)."""
|
|
326
|
+
|
|
327
|
+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
328
|
+
webhook = models.ForeignKey(
|
|
329
|
+
Webhook,
|
|
330
|
+
on_delete=models.CASCADE,
|
|
331
|
+
related_name="deliveries",
|
|
332
|
+
)
|
|
333
|
+
# ``choices`` documents the subscribable domain events; it is validation-only
|
|
334
|
+
# (no DB constraint), which is what lets the reserved PING_EVENT_TYPE — not a
|
|
335
|
+
# WebhookEventType member because nothing can subscribe to it — be recorded
|
|
336
|
+
# here as a real delivery.
|
|
337
|
+
event_type = models.CharField(max_length=30, choices=WebhookEventType.choices)
|
|
338
|
+
payload = models.JSONField()
|
|
339
|
+
# Monotonic per-subscription sequence, allocated once on INSERT and never
|
|
340
|
+
# changed across retries (the same row keeps its number). Sent to the
|
|
341
|
+
# consumer in the X-TruePPM-Webhook-Sequence header so it can detect gaps
|
|
342
|
+
# and reorder out-of-order events. The default of 0 is only a placeholder
|
|
343
|
+
# for the add-column migration; every row created through save() is assigned
|
|
344
|
+
# a real value >= 1 via _next_delivery_sequence().
|
|
345
|
+
sequence_number = models.BigIntegerField(default=0, editable=False)
|
|
346
|
+
status = models.CharField(
|
|
347
|
+
max_length=10,
|
|
348
|
+
choices=DeliveryStatus.choices,
|
|
349
|
+
default=DeliveryStatus.PENDING,
|
|
350
|
+
)
|
|
351
|
+
response_status = models.SmallIntegerField(null=True, blank=True)
|
|
352
|
+
attempt_count = models.SmallIntegerField(default=0)
|
|
353
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
354
|
+
completed_at = models.DateTimeField(null=True, blank=True)
|
|
355
|
+
|
|
356
|
+
class Meta:
|
|
357
|
+
indexes = [
|
|
358
|
+
models.Index(fields=["webhook", "created_at"], name="delivery_webhook_created_idx"),
|
|
359
|
+
models.Index(fields=["status", "created_at"], name="delivery_status_created_idx"),
|
|
360
|
+
]
|
|
361
|
+
ordering = ["-created_at"]
|
|
362
|
+
|
|
363
|
+
def __str__(self) -> str:
|
|
364
|
+
return f"Delivery {self.id} [{self.status}]"
|
|
365
|
+
|
|
366
|
+
def save(self, *args: Any, **kwargs: Any) -> None:
|
|
367
|
+
# Allocate the per-subscription sequence on INSERT only, so the number is
|
|
368
|
+
# stable across the Celery retry chain (deliver_webhook re-saves the same
|
|
369
|
+
# row on every attempt and must not re-number it).
|
|
370
|
+
if self._state.adding and not self.sequence_number:
|
|
371
|
+
self.sequence_number = _next_delivery_sequence(self.webhook_id)
|
|
372
|
+
super().save(*args, **kwargs)
|