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,383 @@
|
|
|
1
|
+
"""Views for webhook management API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
import redis as redis_lib
|
|
8
|
+
from django.db import transaction
|
|
9
|
+
from django.db.models import QuerySet
|
|
10
|
+
from drf_spectacular.utils import (
|
|
11
|
+
OpenApiResponse,
|
|
12
|
+
extend_schema,
|
|
13
|
+
extend_schema_view,
|
|
14
|
+
inline_serializer,
|
|
15
|
+
)
|
|
16
|
+
from kombu.exceptions import ( # type: ignore[import-untyped]
|
|
17
|
+
OperationalError as KombuOperationalError,
|
|
18
|
+
)
|
|
19
|
+
from rest_framework import serializers, status
|
|
20
|
+
from rest_framework.decorators import action
|
|
21
|
+
from rest_framework.mixins import (
|
|
22
|
+
CreateModelMixin,
|
|
23
|
+
DestroyModelMixin,
|
|
24
|
+
ListModelMixin,
|
|
25
|
+
RetrieveModelMixin,
|
|
26
|
+
UpdateModelMixin,
|
|
27
|
+
)
|
|
28
|
+
from rest_framework.pagination import CursorPagination
|
|
29
|
+
from rest_framework.permissions import BasePermission, IsAuthenticated
|
|
30
|
+
from rest_framework.request import Request
|
|
31
|
+
from rest_framework.response import Response
|
|
32
|
+
from rest_framework.serializers import BaseSerializer
|
|
33
|
+
from rest_framework.viewsets import GenericViewSet
|
|
34
|
+
|
|
35
|
+
from trueppm_api.apps.access.permissions import (
|
|
36
|
+
IsProgramAdmin,
|
|
37
|
+
IsProgramMember,
|
|
38
|
+
IsProjectAdmin,
|
|
39
|
+
IsProjectMember,
|
|
40
|
+
IsProjectNotArchived,
|
|
41
|
+
)
|
|
42
|
+
from trueppm_api.apps.idempotency.mixins import IdempotencyMixin
|
|
43
|
+
from trueppm_api.apps.projects.models import Program, Project
|
|
44
|
+
from trueppm_api.apps.webhooks.dispatch import build_delivery_body
|
|
45
|
+
from trueppm_api.apps.webhooks.models import (
|
|
46
|
+
PING_EVENT_TYPE,
|
|
47
|
+
Webhook,
|
|
48
|
+
WebhookDelivery,
|
|
49
|
+
_next_delivery_sequence,
|
|
50
|
+
)
|
|
51
|
+
from trueppm_api.apps.webhooks.serializers import (
|
|
52
|
+
WebhookCreateResponseSerializer,
|
|
53
|
+
WebhookDeliverySerializer,
|
|
54
|
+
WebhookSerializer,
|
|
55
|
+
)
|
|
56
|
+
from trueppm_api.apps.webhooks.tasks import deliver_webhook
|
|
57
|
+
|
|
58
|
+
logger = logging.getLogger(__name__)
|
|
59
|
+
|
|
60
|
+
_BROKER_ERRORS = (KombuOperationalError, ConnectionError, redis_lib.ConnectionError)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class WebhookDeliveryCursorPagination(CursorPagination):
|
|
64
|
+
"""Depth-independent pagination for a webhook's delivery log (#1317).
|
|
65
|
+
|
|
66
|
+
The delivery history grows without bound — one row per event fired — and the
|
|
67
|
+
previous hard ``[:50]`` slice could only ever surface the newest 50 with no
|
|
68
|
+
way to page back. A created_at cursor is stable under the concurrent inserts
|
|
69
|
+
the dispatcher produces. Mirrors AuditEventCursorPagination.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
ordering = "-created_at"
|
|
73
|
+
page_size = 50
|
|
74
|
+
page_size_query_param = "page_size"
|
|
75
|
+
max_page_size = 200
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# The real 200 body of a webhook's delivery log. Declared once and shared by the
|
|
79
|
+
# project- and program-scoped viewsets so both publish the identical component
|
|
80
|
+
# (two `inline_serializer` calls with the same name would be a duplicate-component
|
|
81
|
+
# hazard). This action paginates with a CURSOR while its viewset's own
|
|
82
|
+
# pagination_class is page-number, and drf-spectacular's auto-wrap reads the class
|
|
83
|
+
# attribute — so a `many=True` response was wrapped in the page-number envelope and
|
|
84
|
+
# published `count` as *required* for a body that never carries one. Same defect
|
|
85
|
+
# class as #2583's named instances: a client typing `count` as non-optional breaks
|
|
86
|
+
# on first call. Declare the envelope rather than lean on a heuristic that cannot
|
|
87
|
+
# see which paginator actually runs.
|
|
88
|
+
WEBHOOK_DELIVERY_PAGE = inline_serializer(
|
|
89
|
+
name="WebhookDeliveryCursorPage",
|
|
90
|
+
fields={
|
|
91
|
+
"next": serializers.URLField(allow_null=True),
|
|
92
|
+
"previous": serializers.URLField(allow_null=True),
|
|
93
|
+
"results": WebhookDeliverySerializer(many=True),
|
|
94
|
+
},
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
# The 201 body carries the one-time `secret`; the shared Webhook schema does not
|
|
99
|
+
# declare it (the field is write_only, which is right for every read). Declared per
|
|
100
|
+
#: Both causes of the 403 the project-scoped create now answers. Declared as one string
|
|
101
|
+
#: because the two are indistinguishable on the wire — same status, bare ``detail`` — so
|
|
102
|
+
#: a client branches on the message either way, and publishing only one of them would
|
|
103
|
+
#: tell an integrator the other cannot happen.
|
|
104
|
+
#:
|
|
105
|
+
#: Used ONLY where `ProgramWebhookViewSet` overrides the declaration with its own. A
|
|
106
|
+
#: declaration on an inherited action that the subclass does not re-declare publishes on
|
|
107
|
+
#: BOTH paths — which also collides their pinned operationIds — so `update`,
|
|
108
|
+
#: `partial_update` and `destroy` are deliberately left undeclared here rather than
|
|
109
|
+
#: gaining a project-flavored 403 on the program routes. They are part of the wider
|
|
110
|
+
#: undeclared-403-on-DELETE gap recorded in the MR for #3414.
|
|
111
|
+
_ARCHIVED_OR_NOT_ADMIN = (
|
|
112
|
+
"Caller is not a project Admin, or the project is archived (#3414) — archived "
|
|
113
|
+
"plans are read-only, so a webhook cannot be added, edited, deleted or "
|
|
114
|
+
"test-fired on one. Reading the webhook and its deliveries still works."
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
# viewset because one shared declaration would publish the same operationId on both
|
|
119
|
+
# the project- and program-scoped paths — a duplicate-operationId collision. Ids are
|
|
120
|
+
# pinned for the same reason as `deliveries`: operationId is the *method name* in a
|
|
121
|
+
# generated client, so fixing a response shape must not rename every caller's method
|
|
122
|
+
# (#2583).
|
|
123
|
+
@extend_schema_view(
|
|
124
|
+
create=extend_schema(
|
|
125
|
+
summary="Register a webhook",
|
|
126
|
+
responses={
|
|
127
|
+
201: WebhookCreateResponseSerializer,
|
|
128
|
+
403: OpenApiResponse(description=_ARCHIVED_OR_NOT_ADMIN),
|
|
129
|
+
},
|
|
130
|
+
operation_id="v1_projects_webhooks_create",
|
|
131
|
+
),
|
|
132
|
+
)
|
|
133
|
+
class WebhookViewSet(
|
|
134
|
+
IdempotencyMixin,
|
|
135
|
+
CreateModelMixin,
|
|
136
|
+
RetrieveModelMixin,
|
|
137
|
+
UpdateModelMixin,
|
|
138
|
+
DestroyModelMixin,
|
|
139
|
+
ListModelMixin,
|
|
140
|
+
GenericViewSet[Webhook],
|
|
141
|
+
):
|
|
142
|
+
"""CRUD for outbound webhooks scoped to a project.
|
|
143
|
+
|
|
144
|
+
List/retrieve: requires Viewer+ (IsProjectMember).
|
|
145
|
+
Create/update/delete: requires Admin+ (IsProjectAdmin).
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
serializer_class = WebhookSerializer
|
|
149
|
+
|
|
150
|
+
# Exempt from the generic Idempotency-Key path (ADR-0170): the create response
|
|
151
|
+
# carries the one-time plaintext signing secret, which must never be persisted
|
|
152
|
+
# in the idempotency store for replay. Mirrors ProjectApiTokenViewSet, and it is
|
|
153
|
+
# the same reasoning #2885 encrypts the column for — storing the plaintext in
|
|
154
|
+
# IdempotencyKey.response_body (a plain JSONField, retained for
|
|
155
|
+
# IDEMPOTENCY_RETENTION_HOURS) would move the secret to another table rather than
|
|
156
|
+
# remove it, and would re-serve it on replay, contradicting the documented
|
|
157
|
+
# "returned exactly once" guarantee.
|
|
158
|
+
idempotency_exempt = True
|
|
159
|
+
|
|
160
|
+
def get_permissions(self) -> list[BasePermission]:
|
|
161
|
+
# `deliveries` exposes the full event payload (task notes, comment
|
|
162
|
+
# snippets, assignee emails) of every event ever sent (#903). That is
|
|
163
|
+
# information disclosure beyond plain project membership, so it is
|
|
164
|
+
# Admin-only — consistent with webhook create/update/delete/test, which
|
|
165
|
+
# are already Admin-gated (only an Admin could have created the webhook).
|
|
166
|
+
if self.action == "deliveries":
|
|
167
|
+
# A read. Archived projects stay readable, so no lifecycle gate here.
|
|
168
|
+
return [IsAuthenticated(), IsProjectAdmin()]
|
|
169
|
+
if self.action in ("create", "update", "partial_update", "destroy", "test_ping"):
|
|
170
|
+
# IsProjectNotArchived fires in `has_permission` on these routes because the
|
|
171
|
+
# URL names `project_pk` — the kwarg `_project_pk_from_view` reads (#3414).
|
|
172
|
+
# `destroy` is gated like the rest since the archive bypass was scoped to
|
|
173
|
+
# ProjectViewSet; before that, the router-minted action name alone was
|
|
174
|
+
# enough to skip the check on every project-scoped viewset.
|
|
175
|
+
return [IsAuthenticated(), IsProjectAdmin(), IsProjectNotArchived()]
|
|
176
|
+
return [IsAuthenticated(), IsProjectMember()]
|
|
177
|
+
|
|
178
|
+
def get_queryset(self) -> QuerySet[Webhook]:
|
|
179
|
+
project_pk = self.kwargs["project_pk"]
|
|
180
|
+
return (
|
|
181
|
+
Webhook.objects.filter(project_id=project_pk)
|
|
182
|
+
.select_related("project", "created_by")
|
|
183
|
+
.order_by("-created_at")
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
def perform_create(self, serializer: BaseSerializer[Webhook]) -> None:
|
|
187
|
+
project_pk = self.kwargs["project_pk"]
|
|
188
|
+
try:
|
|
189
|
+
project = Project.objects.get(pk=project_pk, is_deleted=False)
|
|
190
|
+
except Project.DoesNotExist:
|
|
191
|
+
from rest_framework.exceptions import NotFound
|
|
192
|
+
|
|
193
|
+
raise NotFound("Project not found.") from None
|
|
194
|
+
|
|
195
|
+
# Check object-level permission.
|
|
196
|
+
self.check_object_permissions(self.request, project)
|
|
197
|
+
|
|
198
|
+
serializer.save(project=project, created_by=self.request.user)
|
|
199
|
+
|
|
200
|
+
def perform_update(self, serializer: BaseSerializer[Webhook]) -> None:
|
|
201
|
+
serializer.save()
|
|
202
|
+
|
|
203
|
+
def get_object(self) -> Webhook:
|
|
204
|
+
obj: Webhook = super().get_object()
|
|
205
|
+
# Object-level permission check against the webhook's scope object —
|
|
206
|
+
# the Project for project-scoped webhooks, the Program for program-scoped
|
|
207
|
+
# ones (ADR-0076). The active permission classes (Is{Project,Program}*)
|
|
208
|
+
# match because get_permissions is scope-specific per subclass.
|
|
209
|
+
self.check_object_permissions(self.request, self._scope_object(obj))
|
|
210
|
+
return obj
|
|
211
|
+
|
|
212
|
+
def _scope_object(self, webhook: Webhook) -> Project | Program:
|
|
213
|
+
"""Return the Project or Program a webhook is scoped to (XOR)."""
|
|
214
|
+
scope = webhook.program if webhook.program_id else webhook.project
|
|
215
|
+
# The webhook_scope_xor DB constraint guarantees exactly one is set, so
|
|
216
|
+
# scope is never None here — assert narrows the type for mypy.
|
|
217
|
+
assert scope is not None
|
|
218
|
+
return scope
|
|
219
|
+
|
|
220
|
+
@extend_schema(
|
|
221
|
+
summary="Send a test webhook ping",
|
|
222
|
+
responses={
|
|
223
|
+
202: inline_serializer(
|
|
224
|
+
name="WebhookTestPingResponse",
|
|
225
|
+
fields={"delivery_id": serializers.CharField()},
|
|
226
|
+
),
|
|
227
|
+
403: OpenApiResponse(
|
|
228
|
+
description=(
|
|
229
|
+
"Caller lacks Admin on the webhook's scope. On a PROJECT webhook the "
|
|
230
|
+
"same status is also how an archived project refuses (#3414); the "
|
|
231
|
+
"program-scoped route has no archived state. Declared once because "
|
|
232
|
+
"this handler is shared by both viewsets."
|
|
233
|
+
)
|
|
234
|
+
),
|
|
235
|
+
},
|
|
236
|
+
)
|
|
237
|
+
@action(detail=True, methods=["post"], url_path="test")
|
|
238
|
+
def test_ping(self, request: Request, **kwargs: object) -> Response:
|
|
239
|
+
"""Send a test ping to the webhook URL.
|
|
240
|
+
|
|
241
|
+
The ping takes exactly the same path as a real delivery: it renders through
|
|
242
|
+
the subscription's registered format, is signed identically, and consumes a
|
|
243
|
+
sequence number with a matching ``_meta.sequence``. A test therefore fails
|
|
244
|
+
wherever a real delivery would, and never shows up as a gap in the
|
|
245
|
+
consumer's sequence counter.
|
|
246
|
+
|
|
247
|
+
Returns 202 with the ``delivery_id``. That acknowledges the *enqueue*, not
|
|
248
|
+
the receiver's answer — read the ``deliveries`` action back for that row's
|
|
249
|
+
terminal ``status`` and ``response_status`` to learn what the receiver said.
|
|
250
|
+
"""
|
|
251
|
+
# Building the ping inline (rather than through the provider) was how a
|
|
252
|
+
# slack-format webhook — the UI default — got sent a body Slack rejects with
|
|
253
|
+
# 400 invalid_payload while the API reported success on the 202 (#2884).
|
|
254
|
+
webhook = self.get_object()
|
|
255
|
+
sequence = _next_delivery_sequence(webhook.pk)
|
|
256
|
+
body = build_delivery_body(
|
|
257
|
+
webhook,
|
|
258
|
+
PING_EVENT_TYPE,
|
|
259
|
+
{"event": PING_EVENT_TYPE, "webhook_id": str(webhook.pk)},
|
|
260
|
+
sequence,
|
|
261
|
+
)
|
|
262
|
+
delivery = WebhookDelivery.objects.create(
|
|
263
|
+
webhook=webhook,
|
|
264
|
+
event_type=PING_EVENT_TYPE,
|
|
265
|
+
payload=body,
|
|
266
|
+
sequence_number=sequence,
|
|
267
|
+
)
|
|
268
|
+
# Defer dispatch until the delivery row is committed so the task never
|
|
269
|
+
# races against an uncommitted row. If the broker is down the delay()
|
|
270
|
+
# call is a no-op — the delivery row stays PENDING and drain_webhook_queue
|
|
271
|
+
# picks it up within _DRAIN_ORPHAN_MINUTES.
|
|
272
|
+
delivery_id = str(delivery.pk)
|
|
273
|
+
|
|
274
|
+
def _enqueue_ping() -> None:
|
|
275
|
+
try:
|
|
276
|
+
deliver_webhook.delay(delivery_id)
|
|
277
|
+
except _BROKER_ERRORS:
|
|
278
|
+
logger.warning(
|
|
279
|
+
"test_ping: broker unavailable — delivery %s will be drained",
|
|
280
|
+
delivery_id,
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
transaction.on_commit(_enqueue_ping)
|
|
284
|
+
return Response(
|
|
285
|
+
{"delivery_id": delivery_id},
|
|
286
|
+
status=status.HTTP_202_ACCEPTED,
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
@extend_schema(
|
|
290
|
+
summary="List recent webhook deliveries",
|
|
291
|
+
responses={200: WEBHOOK_DELIVERY_PAGE},
|
|
292
|
+
# Pinned because declaring the envelope changes what drf-spectacular
|
|
293
|
+
# thinks this operation is. It derives the operationId's `_list`/
|
|
294
|
+
# `_retrieve` suffix from whether the 200 is a ``many=True`` serializer,
|
|
295
|
+
# so switching to an object envelope silently renamed
|
|
296
|
+
# ``…_deliveries_list`` to ``…_deliveries_retrieve`` — and operationId is
|
|
297
|
+
# the *method name* in a generated client. Fixing a broken response type
|
|
298
|
+
# by breaking every caller's method name would trade one #2583 for
|
|
299
|
+
# another, so the shipped id is held fixed.
|
|
300
|
+
operation_id="v1_projects_webhooks_deliveries_list",
|
|
301
|
+
)
|
|
302
|
+
@action(detail=True, methods=["get"], url_path="deliveries")
|
|
303
|
+
def deliveries(self, request: Request, **kwargs: object) -> Response:
|
|
304
|
+
"""List deliveries for this webhook, newest first (cursor-paginated, #1317).
|
|
305
|
+
|
|
306
|
+
A bare ``GET`` returns the most recent page; follow ``next`` to page
|
|
307
|
+
back through history. Instantiates the cursor paginator directly rather
|
|
308
|
+
than going through ``self.paginator`` (the viewset default is page-number
|
|
309
|
+
for the webhook CRUD list — the delivery log wants a cursor).
|
|
310
|
+
"""
|
|
311
|
+
webhook = self.get_object()
|
|
312
|
+
qs = WebhookDelivery.objects.filter(webhook=webhook)
|
|
313
|
+
paginator = WebhookDeliveryCursorPagination()
|
|
314
|
+
page = paginator.paginate_queryset(qs, request, view=self)
|
|
315
|
+
serializer = WebhookDeliverySerializer(page, many=True)
|
|
316
|
+
return paginator.get_paginated_response(serializer.data)
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
@extend_schema_view(
|
|
320
|
+
# Annotates the INHERITED action rather than redefining it. A single pinned
|
|
321
|
+
# operation_id on the base class would publish the same id on both the
|
|
322
|
+
# project- and program-scoped paths — a duplicate-operationId collision — so
|
|
323
|
+
# each concrete viewset needs its own. `extend_schema_view` does that without
|
|
324
|
+
# a subclass override, which would duplicate the handler for no runtime
|
|
325
|
+
# reason. See WebhookViewSet.deliveries for why the envelope must be explicit
|
|
326
|
+
# and why the id is pinned at all (#2583).
|
|
327
|
+
deliveries=extend_schema(
|
|
328
|
+
summary="List recent webhook deliveries",
|
|
329
|
+
responses={200: WEBHOOK_DELIVERY_PAGE},
|
|
330
|
+
operation_id="v1_programs_webhooks_deliveries_list",
|
|
331
|
+
),
|
|
332
|
+
create=extend_schema(
|
|
333
|
+
summary="Register a webhook",
|
|
334
|
+
responses={201: WebhookCreateResponseSerializer},
|
|
335
|
+
operation_id="v1_programs_webhooks_create",
|
|
336
|
+
),
|
|
337
|
+
)
|
|
338
|
+
class ProgramWebhookViewSet(WebhookViewSet):
|
|
339
|
+
"""CRUD for outbound webhooks scoped to a program (ADR-0076).
|
|
340
|
+
|
|
341
|
+
A program-scoped webhook fires for events on any project within the program.
|
|
342
|
+
Inherits the test/deliveries actions and the rendering/dispatch substrate
|
|
343
|
+
from WebhookViewSet; only the scope resolution and RBAC ladder change:
|
|
344
|
+
list/retrieve require Program Viewer+ (IsProgramMember), mutations require
|
|
345
|
+
Program Admin+ (IsProgramAdmin).
|
|
346
|
+
"""
|
|
347
|
+
|
|
348
|
+
def get_permissions(self) -> list[BasePermission]:
|
|
349
|
+
# `deliveries` exposes the full event payload (task notes, comment
|
|
350
|
+
# snippets, assignee emails) of every event ever sent (#903). That is
|
|
351
|
+
# information disclosure beyond plain project membership, so it is
|
|
352
|
+
# Admin-only — consistent with webhook create/update/delete/test, which
|
|
353
|
+
# are already Admin-gated (only an Admin could have created the webhook).
|
|
354
|
+
if self.action in (
|
|
355
|
+
"create",
|
|
356
|
+
"update",
|
|
357
|
+
"partial_update",
|
|
358
|
+
"destroy",
|
|
359
|
+
"test_ping",
|
|
360
|
+
"deliveries",
|
|
361
|
+
):
|
|
362
|
+
return [IsAuthenticated(), IsProgramAdmin()]
|
|
363
|
+
return [IsAuthenticated(), IsProgramMember()]
|
|
364
|
+
|
|
365
|
+
def get_queryset(self) -> QuerySet[Webhook]:
|
|
366
|
+
program_pk = self.kwargs["program_pk"]
|
|
367
|
+
return (
|
|
368
|
+
Webhook.objects.filter(program_id=program_pk)
|
|
369
|
+
.select_related("program", "created_by")
|
|
370
|
+
.order_by("-created_at")
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
def perform_create(self, serializer: BaseSerializer[Webhook]) -> None:
|
|
374
|
+
program_pk = self.kwargs["program_pk"]
|
|
375
|
+
try:
|
|
376
|
+
program = Program.objects.get(pk=program_pk, is_deleted=False)
|
|
377
|
+
except Program.DoesNotExist:
|
|
378
|
+
from rest_framework.exceptions import NotFound
|
|
379
|
+
|
|
380
|
+
raise NotFound("Program not found.") from None
|
|
381
|
+
|
|
382
|
+
self.check_object_permissions(self.request, program)
|
|
383
|
+
serializer.save(program=program, created_by=self.request.user)
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""App config for the workflow execution engine (ADR-0080).
|
|
2
|
+
|
|
3
|
+
Holds the persistence layer (models, migrations, beat drain) for the default
|
|
4
|
+
workflow backend. The public import surface workflow authors target lives at
|
|
5
|
+
``trueppm_api.workflows`` (interface, services, backends, registry); this app
|
|
6
|
+
is a backend implementation detail those consumers never import directly, which
|
|
7
|
+
is what preserves backend-neutrality (ADR-0080 §E).
|
|
8
|
+
|
|
9
|
+
Named ``workflow_engine`` (not ``workflows``) so the app label does not collide
|
|
10
|
+
with the ``trueppm_api.workflows`` public package — that collision confused tools
|
|
11
|
+
that map app label → module (the mypy django-stubs plugin stopped recognizing
|
|
12
|
+
these as models), and is avoided by naming the engine's persistence app and the
|
|
13
|
+
public API package distinctly.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from django.apps import AppConfig
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class WorkflowEngineConfig(AppConfig):
|
|
22
|
+
default_auto_field = "django.db.models.BigAutoField"
|
|
23
|
+
name = "trueppm_api.apps.workflow_engine"
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# Generated by Django 6.0.3 on 2026-05-24 11:50
|
|
2
|
+
|
|
3
|
+
import uuid
|
|
4
|
+
|
|
5
|
+
import django.db.models.deletion
|
|
6
|
+
from django.db import migrations, models
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Migration(migrations.Migration):
|
|
10
|
+
initial = True
|
|
11
|
+
|
|
12
|
+
dependencies = []
|
|
13
|
+
|
|
14
|
+
operations = [
|
|
15
|
+
migrations.CreateModel(
|
|
16
|
+
name="WorkflowInstance",
|
|
17
|
+
fields=[
|
|
18
|
+
(
|
|
19
|
+
"id",
|
|
20
|
+
models.UUIDField(
|
|
21
|
+
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
|
|
22
|
+
),
|
|
23
|
+
),
|
|
24
|
+
("name", models.CharField(db_index=True, max_length=255)),
|
|
25
|
+
(
|
|
26
|
+
"status",
|
|
27
|
+
models.CharField(
|
|
28
|
+
choices=[
|
|
29
|
+
("pending", "Pending"),
|
|
30
|
+
("running", "Running"),
|
|
31
|
+
("sleeping", "Sleeping"),
|
|
32
|
+
("completed", "Completed"),
|
|
33
|
+
("failed", "Failed"),
|
|
34
|
+
("canceled", "Canceled"),
|
|
35
|
+
],
|
|
36
|
+
default="pending",
|
|
37
|
+
max_length=16,
|
|
38
|
+
),
|
|
39
|
+
),
|
|
40
|
+
("input", models.JSONField(default=dict)),
|
|
41
|
+
("result", models.JSONField(blank=True, null=True)),
|
|
42
|
+
("error", models.TextField(blank=True, default="")),
|
|
43
|
+
("idempotency_key", models.CharField(max_length=64)),
|
|
44
|
+
("cancel_reason", models.CharField(blank=True, default="", max_length=255)),
|
|
45
|
+
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
46
|
+
("updated_at", models.DateTimeField(auto_now=True)),
|
|
47
|
+
("completed_at", models.DateTimeField(blank=True, null=True)),
|
|
48
|
+
],
|
|
49
|
+
options={
|
|
50
|
+
"indexes": [
|
|
51
|
+
models.Index(fields=["status", "created_at"], name="workflow_status_idx"),
|
|
52
|
+
models.Index(fields=["completed_at"], name="workflow_completed_idx"),
|
|
53
|
+
],
|
|
54
|
+
"constraints": [
|
|
55
|
+
models.UniqueConstraint(
|
|
56
|
+
fields=("idempotency_key",), name="workflow_idempotency_key_uniq"
|
|
57
|
+
)
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
),
|
|
61
|
+
migrations.CreateModel(
|
|
62
|
+
name="WorkflowHistoryEvent",
|
|
63
|
+
fields=[
|
|
64
|
+
(
|
|
65
|
+
"id",
|
|
66
|
+
models.UUIDField(
|
|
67
|
+
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
|
|
68
|
+
),
|
|
69
|
+
),
|
|
70
|
+
("seq", models.BigIntegerField()),
|
|
71
|
+
("event_type", models.CharField(max_length=32)),
|
|
72
|
+
("payload", models.JSONField(default=dict)),
|
|
73
|
+
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
74
|
+
(
|
|
75
|
+
"workflow",
|
|
76
|
+
models.ForeignKey(
|
|
77
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
78
|
+
related_name="history",
|
|
79
|
+
to="workflow_engine.workflowinstance",
|
|
80
|
+
),
|
|
81
|
+
),
|
|
82
|
+
],
|
|
83
|
+
options={
|
|
84
|
+
"ordering": ["workflow_id", "seq"],
|
|
85
|
+
"indexes": [
|
|
86
|
+
models.Index(fields=["workflow", "seq"], name="workflow_history_lookup_idx"),
|
|
87
|
+
models.Index(fields=["created_at"], name="workflow_history_retention_idx"),
|
|
88
|
+
],
|
|
89
|
+
"constraints": [
|
|
90
|
+
models.UniqueConstraint(
|
|
91
|
+
fields=("workflow", "seq"), name="workflow_history_seq_uniq"
|
|
92
|
+
)
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
),
|
|
96
|
+
migrations.CreateModel(
|
|
97
|
+
name="WorkflowActivityExecution",
|
|
98
|
+
fields=[
|
|
99
|
+
(
|
|
100
|
+
"id",
|
|
101
|
+
models.UUIDField(
|
|
102
|
+
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
|
|
103
|
+
),
|
|
104
|
+
),
|
|
105
|
+
("activity_name", models.CharField(max_length=255)),
|
|
106
|
+
("input_hash", models.CharField(max_length=64)),
|
|
107
|
+
("result", models.JSONField(blank=True, null=True)),
|
|
108
|
+
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
109
|
+
(
|
|
110
|
+
"workflow",
|
|
111
|
+
models.ForeignKey(
|
|
112
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
113
|
+
related_name="activity_executions",
|
|
114
|
+
to="workflow_engine.workflowinstance",
|
|
115
|
+
),
|
|
116
|
+
),
|
|
117
|
+
],
|
|
118
|
+
options={
|
|
119
|
+
"constraints": [
|
|
120
|
+
models.UniqueConstraint(
|
|
121
|
+
fields=("workflow", "activity_name", "input_hash"),
|
|
122
|
+
name="workflow_activity_oaoo_uniq",
|
|
123
|
+
)
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
),
|
|
127
|
+
migrations.CreateModel(
|
|
128
|
+
name="WorkflowOutboxRow",
|
|
129
|
+
fields=[
|
|
130
|
+
(
|
|
131
|
+
"id",
|
|
132
|
+
models.UUIDField(
|
|
133
|
+
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
|
|
134
|
+
),
|
|
135
|
+
),
|
|
136
|
+
("step_name", models.CharField(max_length=255)),
|
|
137
|
+
("step_input", models.JSONField(default=dict)),
|
|
138
|
+
(
|
|
139
|
+
"status",
|
|
140
|
+
models.CharField(
|
|
141
|
+
choices=[
|
|
142
|
+
("pending", "Pending"),
|
|
143
|
+
("dispatched", "Dispatched"),
|
|
144
|
+
("done", "Done"),
|
|
145
|
+
("dead", "Dead"),
|
|
146
|
+
],
|
|
147
|
+
default="pending",
|
|
148
|
+
max_length=16,
|
|
149
|
+
),
|
|
150
|
+
),
|
|
151
|
+
("attempt_count", models.PositiveIntegerField(default=0)),
|
|
152
|
+
("celery_task_id", models.CharField(blank=True, default="", max_length=255)),
|
|
153
|
+
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
154
|
+
("dispatched_at", models.DateTimeField(blank=True, null=True)),
|
|
155
|
+
(
|
|
156
|
+
"workflow",
|
|
157
|
+
models.ForeignKey(
|
|
158
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
159
|
+
related_name="outbox_rows",
|
|
160
|
+
to="workflow_engine.workflowinstance",
|
|
161
|
+
),
|
|
162
|
+
),
|
|
163
|
+
],
|
|
164
|
+
options={
|
|
165
|
+
"ordering": ["created_at"],
|
|
166
|
+
"indexes": [
|
|
167
|
+
models.Index(fields=["status", "created_at"], name="workflow_outbox_drain_idx")
|
|
168
|
+
],
|
|
169
|
+
},
|
|
170
|
+
),
|
|
171
|
+
migrations.CreateModel(
|
|
172
|
+
name="WorkflowSignal",
|
|
173
|
+
fields=[
|
|
174
|
+
(
|
|
175
|
+
"id",
|
|
176
|
+
models.UUIDField(
|
|
177
|
+
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
|
|
178
|
+
),
|
|
179
|
+
),
|
|
180
|
+
("signal_name", models.CharField(max_length=255)),
|
|
181
|
+
("payload", models.JSONField(default=dict)),
|
|
182
|
+
("consumed", models.BooleanField(default=False)),
|
|
183
|
+
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
184
|
+
(
|
|
185
|
+
"workflow",
|
|
186
|
+
models.ForeignKey(
|
|
187
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
188
|
+
related_name="signals",
|
|
189
|
+
to="workflow_engine.workflowinstance",
|
|
190
|
+
),
|
|
191
|
+
),
|
|
192
|
+
],
|
|
193
|
+
options={
|
|
194
|
+
"ordering": ["created_at"],
|
|
195
|
+
"indexes": [
|
|
196
|
+
models.Index(
|
|
197
|
+
fields=["workflow", "consumed", "created_at"],
|
|
198
|
+
name="workflow_signal_inbox_idx",
|
|
199
|
+
)
|
|
200
|
+
],
|
|
201
|
+
},
|
|
202
|
+
),
|
|
203
|
+
migrations.CreateModel(
|
|
204
|
+
name="WorkflowTimer",
|
|
205
|
+
fields=[
|
|
206
|
+
(
|
|
207
|
+
"id",
|
|
208
|
+
models.UUIDField(
|
|
209
|
+
default=uuid.uuid4, editable=False, primary_key=True, serialize=False
|
|
210
|
+
),
|
|
211
|
+
),
|
|
212
|
+
("fire_at", models.DateTimeField()),
|
|
213
|
+
("fired", models.BooleanField(default=False)),
|
|
214
|
+
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
215
|
+
(
|
|
216
|
+
"workflow",
|
|
217
|
+
models.ForeignKey(
|
|
218
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
219
|
+
related_name="timers",
|
|
220
|
+
to="workflow_engine.workflowinstance",
|
|
221
|
+
),
|
|
222
|
+
),
|
|
223
|
+
],
|
|
224
|
+
options={
|
|
225
|
+
"indexes": [
|
|
226
|
+
models.Index(fields=["fired", "fire_at"], name="workflow_timer_due_idx")
|
|
227
|
+
],
|
|
228
|
+
},
|
|
229
|
+
),
|
|
230
|
+
]
|