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,231 @@
|
|
|
1
|
+
"""Match a verified Git webhook to a task and move its card (#329, ADR-0158).
|
|
2
|
+
|
|
3
|
+
The service is the second half of the OSS Git-event automation: given an already
|
|
4
|
+
**verified** :class:`~trueppm_api.apps.integrations.git_webhook_auth.GitWebhookEnvelope`,
|
|
5
|
+
it finds the task that carries the PR/MR as a ``TaskLink`` and advances that card's
|
|
6
|
+
board status **forward only** through the canonical ``TaskSerializer`` write path.
|
|
7
|
+
|
|
8
|
+
Three deliberate properties (all enforced here, none bypassable):
|
|
9
|
+
|
|
10
|
+
- **Forward-only.** ``pr.opened`` moves a card to REVIEW only from
|
|
11
|
+
``{BACKLOG, NOT_STARTED, IN_PROGRESS}``; ``pr.merged`` moves to COMPLETE only
|
|
12
|
+
from a non-COMPLETE state. A card already at/ahead of the target is a no-op —
|
|
13
|
+
automation never drags a card backward, and a redelivered webhook is idempotent
|
|
14
|
+
even if the Redis dedup layer is unavailable.
|
|
15
|
+
- **No parallel transition path.** The status write goes through ``TaskSerializer``
|
|
16
|
+
exactly as a human board drag would, so every existing validation, actual-date
|
|
17
|
+
rule (ADR-0136), and the advisory WIP surface (ADR-0039) apply unchanged. The
|
|
18
|
+
automation neither adds nor circumvents a hard WIP block (there is none).
|
|
19
|
+
- **Attributed but classified as automation.** The write is attributed to the
|
|
20
|
+
configuring admin (``configured_by``) for RBAC + history, while
|
|
21
|
+
``history_change_reason`` tags it ``git:pr_*`` so the ADR-0096 activity timeline
|
|
22
|
+
shows it as an automated move, and the WS broadcast carries ``actor_id=None``
|
|
23
|
+
(ADR-0152 system write).
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import functools
|
|
29
|
+
from dataclasses import dataclass
|
|
30
|
+
from typing import TYPE_CHECKING, Any
|
|
31
|
+
|
|
32
|
+
from django.db import transaction
|
|
33
|
+
|
|
34
|
+
from trueppm_api.apps.access.models import Role
|
|
35
|
+
from trueppm_api.apps.projects.models import Task, TaskStatus
|
|
36
|
+
|
|
37
|
+
from .git_webhook_auth import GIT_EVENT_PR_MERGED, GIT_EVENT_PR_OPENED, PROVIDER_GITHUB
|
|
38
|
+
from .models import BoardAutomation, TaskLink
|
|
39
|
+
from .providers import _parse_github_url, _parse_gitlab_url
|
|
40
|
+
|
|
41
|
+
if TYPE_CHECKING:
|
|
42
|
+
from django.contrib.auth.models import AbstractBaseUser
|
|
43
|
+
|
|
44
|
+
# pr.opened may advance a card to REVIEW only from these states — never from
|
|
45
|
+
# REVIEW or COMPLETE (forward-only). pr.merged may advance to COMPLETE from any
|
|
46
|
+
# non-COMPLETE state.
|
|
47
|
+
_PR_OPENED_SOURCE_STATES = frozenset(
|
|
48
|
+
{TaskStatus.BACKLOG, TaskStatus.NOT_STARTED, TaskStatus.IN_PROGRESS}
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
_CHANGE_REASON = {
|
|
52
|
+
GIT_EVENT_PR_OPENED: "git:pr_opened",
|
|
53
|
+
GIT_EVENT_PR_MERGED: "git:pr_merged",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass(frozen=True)
|
|
58
|
+
class GitAutomationResult:
|
|
59
|
+
"""Outcome of applying one webhook, shaped for the receiver's JSON response."""
|
|
60
|
+
|
|
61
|
+
matched: bool
|
|
62
|
+
moved: bool
|
|
63
|
+
task_id: str | None = None
|
|
64
|
+
from_status: str | None = None
|
|
65
|
+
to_status: str | None = None
|
|
66
|
+
reason: str = ""
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class _SystemRequest:
|
|
70
|
+
"""Minimal request stand-in for the serializer's ``context["request"]``.
|
|
71
|
+
|
|
72
|
+
The ``TaskSerializer`` only reads ``request.user`` (for role/history); a full
|
|
73
|
+
DRF request is unnecessary and would drag in middleware. ``caller_role`` is
|
|
74
|
+
passed alongside in context so role resolution never hits the DB for a user
|
|
75
|
+
who may since have been removed from the project.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def __init__(self, user: AbstractBaseUser | None) -> None:
|
|
79
|
+
self.user = user
|
|
80
|
+
self.method = "PATCH"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _pr_key(provider: str, url: str) -> tuple[Any, ...] | None:
|
|
84
|
+
"""Parse a PR/MR URL into a comparable identity tuple, or ``None``.
|
|
85
|
+
|
|
86
|
+
Reuses the existing ``providers`` parsers. For GitHub the resource must be a
|
|
87
|
+
``pull`` (issues share the URL shape but are not board-moving PRs); for GitLab
|
|
88
|
+
the parser already restricts to ``merge_requests``/``issues`` and we keep only
|
|
89
|
+
merge requests.
|
|
90
|
+
"""
|
|
91
|
+
# Both providers return a ``(repo_path, ref)`` pair so the tuple shape is
|
|
92
|
+
# identical regardless of provider (GitHub owner/repo is folded into one
|
|
93
|
+
# path segment, mirroring GitLab's already-combined project path).
|
|
94
|
+
if provider == PROVIDER_GITHUB:
|
|
95
|
+
parsed = _parse_github_url(url)
|
|
96
|
+
if parsed is None:
|
|
97
|
+
return None
|
|
98
|
+
owner, repo, kind, ref = parsed
|
|
99
|
+
if kind != "pull":
|
|
100
|
+
return None
|
|
101
|
+
return (f"{owner.lower()}/{repo.lower()}", ref)
|
|
102
|
+
parsed_gl = _parse_gitlab_url(url)
|
|
103
|
+
if parsed_gl is None:
|
|
104
|
+
return None
|
|
105
|
+
project_path, kind, ref = parsed_gl
|
|
106
|
+
if kind != "merge_requests":
|
|
107
|
+
return None
|
|
108
|
+
return (project_path.lower(), ref)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _find_linked_task(project_id: Any, provider: str, pr_url: str) -> Task | None:
|
|
112
|
+
"""Find the project task whose ``TaskLink`` points at ``pr_url``.
|
|
113
|
+
|
|
114
|
+
No normalized URL column exists (ADR-0158 §4), so this is parse-and-compare,
|
|
115
|
+
bounded to the project's own links — the candidate set is small. The first
|
|
116
|
+
matching link wins; a task linked to the same PR more than once is degenerate.
|
|
117
|
+
"""
|
|
118
|
+
target = _pr_key(provider, pr_url)
|
|
119
|
+
if target is None:
|
|
120
|
+
return None
|
|
121
|
+
candidates = (
|
|
122
|
+
TaskLink.objects.filter(
|
|
123
|
+
task__project_id=project_id,
|
|
124
|
+
is_deleted=False,
|
|
125
|
+
task__is_deleted=False,
|
|
126
|
+
)
|
|
127
|
+
.select_related("task")
|
|
128
|
+
.only("url", "task")
|
|
129
|
+
)
|
|
130
|
+
for link in candidates:
|
|
131
|
+
if _pr_key(provider, link.url) == target:
|
|
132
|
+
return link.task
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _target_status(event: str, current: str) -> str | None:
|
|
137
|
+
"""Resolve the forward-only target board status, or ``None`` for a no-op."""
|
|
138
|
+
if event == GIT_EVENT_PR_OPENED:
|
|
139
|
+
return TaskStatus.REVIEW if current in _PR_OPENED_SOURCE_STATES else None
|
|
140
|
+
if event == GIT_EVENT_PR_MERGED:
|
|
141
|
+
return TaskStatus.COMPLETE if current != TaskStatus.COMPLETE else None
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def apply_git_event_to_card(
|
|
146
|
+
automation: BoardAutomation, provider: str, event: str, pr_url: str | None
|
|
147
|
+
) -> GitAutomationResult:
|
|
148
|
+
"""Apply one verified Git event to its linked card.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
automation: the enabled ``BoardAutomation`` for the target project.
|
|
152
|
+
provider: ``"github"`` / ``"gitlab"`` (from the verified envelope).
|
|
153
|
+
event: ``pr.opened`` / ``pr.merged``.
|
|
154
|
+
pr_url: the PR/MR URL from the payload.
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
A :class:`GitAutomationResult` describing what happened. Always a 200-shaped
|
|
158
|
+
result — "no link" and "already there" are normal, not errors.
|
|
159
|
+
"""
|
|
160
|
+
if not pr_url:
|
|
161
|
+
return GitAutomationResult(matched=False, moved=False, reason="no_url")
|
|
162
|
+
|
|
163
|
+
task = _find_linked_task(automation.project_id, provider, pr_url)
|
|
164
|
+
if task is None:
|
|
165
|
+
return GitAutomationResult(matched=False, moved=False, reason="no_link")
|
|
166
|
+
|
|
167
|
+
current = task.status
|
|
168
|
+
target = _target_status(event, current)
|
|
169
|
+
if target is None:
|
|
170
|
+
# Forward-only no-op: the card is already at or ahead of the target.
|
|
171
|
+
return GitAutomationResult(
|
|
172
|
+
matched=True,
|
|
173
|
+
moved=False,
|
|
174
|
+
task_id=str(task.pk),
|
|
175
|
+
from_status=current,
|
|
176
|
+
to_status=current,
|
|
177
|
+
reason="noop_forward_only",
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
moved_task = _write_status(task, target, event, automation.configured_by)
|
|
181
|
+
return GitAutomationResult(
|
|
182
|
+
matched=True,
|
|
183
|
+
moved=True,
|
|
184
|
+
task_id=str(moved_task.pk),
|
|
185
|
+
from_status=current,
|
|
186
|
+
to_status=target,
|
|
187
|
+
reason="opened_review" if event == GIT_EVENT_PR_OPENED else "merged_complete",
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _write_status(task: Task, target: str, event: str, actor: AbstractBaseUser | None) -> Task:
|
|
192
|
+
"""Write the new status through the canonical serializer + broadcast the move.
|
|
193
|
+
|
|
194
|
+
Runs inside ``transaction.atomic`` so the status write and the deferred
|
|
195
|
+
broadcast commit together. ``_change_reason`` / ``_history_user`` are stamped
|
|
196
|
+
on the instance before ``save()`` so ``django-simple-history`` attributes the
|
|
197
|
+
edit to the configuring admin and classifies it as automation (ADR-0096).
|
|
198
|
+
"""
|
|
199
|
+
# Imported lazily — the projects serializer module is heavy and importing it
|
|
200
|
+
# at module load would create an apps-not-ready risk during migrations.
|
|
201
|
+
from trueppm_api.apps.projects.serializers import TaskSerializer
|
|
202
|
+
from trueppm_api.apps.sync.broadcast import broadcast_task_updated
|
|
203
|
+
|
|
204
|
+
with transaction.atomic():
|
|
205
|
+
task._change_reason = _CHANGE_REASON[event] # type: ignore[attr-defined]
|
|
206
|
+
task._history_user = actor # type: ignore[attr-defined]
|
|
207
|
+
serializer = TaskSerializer(
|
|
208
|
+
instance=task,
|
|
209
|
+
data={"status": target},
|
|
210
|
+
partial=True,
|
|
211
|
+
context={"request": _SystemRequest(actor), "caller_role": Role.ADMIN},
|
|
212
|
+
)
|
|
213
|
+
serializer.is_valid(raise_exception=True)
|
|
214
|
+
updated: Task = serializer.save()
|
|
215
|
+
|
|
216
|
+
project_id_str = str(updated.project_id)
|
|
217
|
+
task_id_str = str(updated.pk)
|
|
218
|
+
version = updated.server_version
|
|
219
|
+
# actor_id=None — automation/system write (ADR-0152). Deferred to commit
|
|
220
|
+
# so a connected board refreshes the card without a manual refetch.
|
|
221
|
+
transaction.on_commit(
|
|
222
|
+
functools.partial(
|
|
223
|
+
broadcast_task_updated,
|
|
224
|
+
project_id_str,
|
|
225
|
+
task_id=task_id_str,
|
|
226
|
+
changed_fields=["status"],
|
|
227
|
+
version=version,
|
|
228
|
+
actor_id=None,
|
|
229
|
+
)
|
|
230
|
+
)
|
|
231
|
+
return updated
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"""Inbound Git-webhook authentication + envelope parsing (#329, ADR-0158).
|
|
2
|
+
|
|
3
|
+
This module is the security boundary of the OSS Git-event board-card automation.
|
|
4
|
+
It has two jobs and nothing else:
|
|
5
|
+
|
|
6
|
+
1. **Verify the per-provider signature** against the project's plaintext secret —
|
|
7
|
+
constant-time, before the payload is ever interpreted as a command. GitHub
|
|
8
|
+
recomputes ``HMAC-SHA256`` over the *raw* request body; GitLab compares the
|
|
9
|
+
``X-Gitlab-Token`` header directly. A bad or missing signature is rejected with
|
|
10
|
+
the receiver's uniform 404 (see :class:`WebhookSignatureError`).
|
|
11
|
+
|
|
12
|
+
2. **Normalize the payload** into a small :class:`GitWebhookEnvelope` — provider,
|
|
13
|
+
forward-only event (``pr.opened`` / ``pr.merged`` / ``None`` = ignore), the
|
|
14
|
+
PR/MR URL to match against a ``TaskLink``, and an idempotency ``delivery_key``.
|
|
15
|
+
|
|
16
|
+
It deliberately does **not** touch the database, mutate any task, or call out to
|
|
17
|
+
a provider — the receiver (``views.GitWebhookIngestView``) and the service
|
|
18
|
+
(``git_automation_services``) own that. Keeping verification pure and side-effect
|
|
19
|
+
free is what makes it cheap to test exhaustively (the bug class that matters here
|
|
20
|
+
is "signature accepted when it should not have been").
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import hashlib
|
|
26
|
+
import hmac
|
|
27
|
+
from dataclasses import dataclass
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
from trueppm_api.core.constant_time import constant_time_equal
|
|
31
|
+
|
|
32
|
+
# Forward-only event vocabulary. Only these two map to a card move; every other
|
|
33
|
+
# Git event resolves to ``None`` and the receiver returns a 200 "ignored".
|
|
34
|
+
GIT_EVENT_PR_OPENED = "pr.opened"
|
|
35
|
+
GIT_EVENT_PR_MERGED = "pr.merged"
|
|
36
|
+
|
|
37
|
+
PROVIDER_GITHUB = "github"
|
|
38
|
+
PROVIDER_GITLAB = "gitlab"
|
|
39
|
+
|
|
40
|
+
# GitHub ``pull_request`` actions that mean "a review is now wanted". ``reopened``
|
|
41
|
+
# and ``ready_for_review`` are included so a draft promoted to ready, or a closed
|
|
42
|
+
# PR reopened, re-asserts the REVIEW intent (the forward-only guard still prevents
|
|
43
|
+
# moving a card backward).
|
|
44
|
+
_GITHUB_OPEN_ACTIONS = frozenset({"opened", "reopened", "ready_for_review"})
|
|
45
|
+
# GitLab ``merge_request`` actions with the same meaning.
|
|
46
|
+
_GITLAB_OPEN_ACTIONS = frozenset({"open", "reopen"})
|
|
47
|
+
|
|
48
|
+
# ``GitWebhookEnvelope.ignored_reason`` value for a suppressed draft/WIP open.
|
|
49
|
+
_IGNORED_DRAFT = "draft"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True)
|
|
53
|
+
class GitWebhookEnvelope:
|
|
54
|
+
"""Provider-neutral view of a verified webhook, ready for matching."""
|
|
55
|
+
|
|
56
|
+
provider: str
|
|
57
|
+
# GIT_EVENT_PR_OPENED | GIT_EVENT_PR_MERGED | None (irrelevant event → ignore).
|
|
58
|
+
event: str | None
|
|
59
|
+
# The PR/MR URL to match against an existing TaskLink. None when the payload
|
|
60
|
+
# carries no usable URL (then there is nothing to match and we 200-ignore).
|
|
61
|
+
pr_url: str | None
|
|
62
|
+
# Stable per-delivery key for Redis dedup. Always non-empty.
|
|
63
|
+
delivery_key: str
|
|
64
|
+
# The provider's own event name (for the "ignored" response and logging).
|
|
65
|
+
raw_event_name: str
|
|
66
|
+
# Why an otherwise-actionable event resolved to ``event=None``. Empty for an
|
|
67
|
+
# event we never act on (a push, a comment); ``"draft"`` when the payload was a
|
|
68
|
+
# PR/MR *open* on a draft/WIP request, which deliberately does not promote the
|
|
69
|
+
# card (#2882). Surfaced in the receiver's 200 body and the delivery record so
|
|
70
|
+
# "why didn't my card move?" has an answer that is not "read the source".
|
|
71
|
+
ignored_reason: str = ""
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class WebhookSignatureError(Exception):
|
|
75
|
+
"""Raised when a webhook signature is missing or does not verify.
|
|
76
|
+
|
|
77
|
+
Carries no provider/secret detail — the receiver maps it to the same bare 404 it
|
|
78
|
+
returns for "no automation configured", so a caller genuinely cannot distinguish
|
|
79
|
+
the two. It used to map to a 401, which *named* the distinction the 404 was
|
|
80
|
+
written to hide: 401 meant enabled-with-a-secret, 404 meant not configured, and
|
|
81
|
+
any project Viewer could read off admin-only state from a project UUID (#2881).
|
|
82
|
+
The reason a delivery was rejected is not lost — it lands in the structured
|
|
83
|
+
receiver log and on ``BoardAutomation.last_delivery_outcome``, both of which
|
|
84
|
+
require the caller to already be an admin of that project.
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def detect_provider(headers: Any) -> str | None:
|
|
89
|
+
"""Return ``"github"`` / ``"gitlab"`` from the request headers, or ``None``.
|
|
90
|
+
|
|
91
|
+
Detection is by the provider's own event header, not a query param, so a
|
|
92
|
+
caller cannot spoof the provider to pick a weaker verification path —
|
|
93
|
+
each provider's branch only accepts its own signature header.
|
|
94
|
+
"""
|
|
95
|
+
if headers.get("X-GitHub-Event"):
|
|
96
|
+
return PROVIDER_GITHUB
|
|
97
|
+
if headers.get("X-Gitlab-Event"):
|
|
98
|
+
return PROVIDER_GITLAB
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def verify_signature(
|
|
103
|
+
provider: str,
|
|
104
|
+
secret_plaintext: str,
|
|
105
|
+
raw_body: bytes,
|
|
106
|
+
headers: Any,
|
|
107
|
+
) -> None:
|
|
108
|
+
"""Verify the provider signature in constant time, or raise.
|
|
109
|
+
|
|
110
|
+
GitHub: ``X-Hub-Signature-256`` must equal ``"sha256=" + HMAC_SHA256(secret,
|
|
111
|
+
raw_body)``. The raw body (not the parsed dict) is hashed because re-encoding
|
|
112
|
+
a parsed payload would not byte-match what the provider signed.
|
|
113
|
+
|
|
114
|
+
GitLab: ``X-Gitlab-Token`` must equal the shared secret. GitLab sends the
|
|
115
|
+
secret verbatim rather than an HMAC, so this is a direct constant-time compare.
|
|
116
|
+
|
|
117
|
+
Raises:
|
|
118
|
+
WebhookSignatureError: signature header absent, malformed, or mismatched.
|
|
119
|
+
No secret bytes leak in the message.
|
|
120
|
+
"""
|
|
121
|
+
if not secret_plaintext:
|
|
122
|
+
# No secret configured → nothing can verify → treat as unauthenticated.
|
|
123
|
+
raise WebhookSignatureError("no secret configured")
|
|
124
|
+
|
|
125
|
+
if provider == PROVIDER_GITHUB:
|
|
126
|
+
provided = headers.get("X-Hub-Signature-256") or ""
|
|
127
|
+
digest = hmac.new(secret_plaintext.encode("utf-8"), raw_body, hashlib.sha256).hexdigest()
|
|
128
|
+
expected = f"sha256={digest}"
|
|
129
|
+
if not constant_time_equal(provided, expected):
|
|
130
|
+
raise WebhookSignatureError("github signature mismatch")
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
if provider == PROVIDER_GITLAB:
|
|
134
|
+
provided = headers.get("X-Gitlab-Token") or ""
|
|
135
|
+
if not constant_time_equal(provided, secret_plaintext):
|
|
136
|
+
raise WebhookSignatureError("gitlab token mismatch")
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
raise WebhookSignatureError(f"unsupported provider {provider!r}")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def parse_envelope(provider: str, headers: Any, payload: Any) -> GitWebhookEnvelope:
|
|
143
|
+
"""Normalize a verified webhook into a :class:`GitWebhookEnvelope`.
|
|
144
|
+
|
|
145
|
+
Never raises on an unrecognized event — an event we do not act on is normal
|
|
146
|
+
traffic (the provider sends every configured event), so it resolves to
|
|
147
|
+
``event=None`` and the receiver returns 200. A malformed *body* (not a dict)
|
|
148
|
+
is the caller's concern; this function assumes ``payload`` is a parsed object.
|
|
149
|
+
"""
|
|
150
|
+
if provider == PROVIDER_GITHUB:
|
|
151
|
+
return _parse_github(headers, payload)
|
|
152
|
+
if provider == PROVIDER_GITLAB:
|
|
153
|
+
return _parse_gitlab(payload)
|
|
154
|
+
return GitWebhookEnvelope(
|
|
155
|
+
provider=provider, event=None, pr_url=None, delivery_key="", raw_event_name=""
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _github_event(action: str, pr: Any, is_draft: bool) -> tuple[str | None, str]:
|
|
160
|
+
"""Resolve a GitHub ``pull_request`` action to ``(event, ignored_reason)``.
|
|
161
|
+
|
|
162
|
+
Both halves are returned together because they are one decision: an action we
|
|
163
|
+
act on yields an event and no reason, and a suppressed draft open yields no
|
|
164
|
+
event *plus* the reason it was suppressed. Anything else is normal traffic we
|
|
165
|
+
never act on — ``(None, "")``, with no reason to report.
|
|
166
|
+
"""
|
|
167
|
+
if action in _GITHUB_OPEN_ACTIONS:
|
|
168
|
+
if is_draft:
|
|
169
|
+
return None, _IGNORED_DRAFT
|
|
170
|
+
return GIT_EVENT_PR_OPENED, ""
|
|
171
|
+
if action == "closed" and isinstance(pr, dict) and pr.get("merged") is True:
|
|
172
|
+
# A merged PR is never a draft; merge always completes the card.
|
|
173
|
+
return GIT_EVENT_PR_MERGED, ""
|
|
174
|
+
return None, ""
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def _parse_github(headers: Any, payload: dict[str, Any]) -> GitWebhookEnvelope:
|
|
178
|
+
event_name = headers.get("X-GitHub-Event") or ""
|
|
179
|
+
# X-GitHub-Delivery is a per-delivery UUID — the natural idempotency key.
|
|
180
|
+
delivery_key = headers.get("X-GitHub-Delivery") or ""
|
|
181
|
+
pr = payload.get("pull_request") if isinstance(payload, dict) else None
|
|
182
|
+
pr_url = pr.get("html_url") if isinstance(pr, dict) else None
|
|
183
|
+
action = payload.get("action") if isinstance(payload, dict) else None
|
|
184
|
+
# GitHub marks a draft PR with ``pull_request.draft``. A draft opened to run CI
|
|
185
|
+
# is not a request for review, and the forward-only rule means a card promoted
|
|
186
|
+
# by mistake can never come back — so a draft open is deliberately inert and
|
|
187
|
+
# the promotion happens on the ``ready_for_review`` action instead (#2882).
|
|
188
|
+
is_draft = bool(pr.get("draft")) if isinstance(pr, dict) else False
|
|
189
|
+
|
|
190
|
+
event: str | None = None
|
|
191
|
+
ignored_reason = ""
|
|
192
|
+
if event_name == "pull_request" and isinstance(action, str):
|
|
193
|
+
event, ignored_reason = _github_event(action, pr, is_draft)
|
|
194
|
+
|
|
195
|
+
return GitWebhookEnvelope(
|
|
196
|
+
provider=PROVIDER_GITHUB,
|
|
197
|
+
event=event,
|
|
198
|
+
# Narrowed for the same reason the GitLab arm below narrows: a signed but
|
|
199
|
+
# hostile/garbled payload can put any JSON type in ``html_url``, and every
|
|
200
|
+
# consumer downstream (``_pr_key`` → ``urlparse``) assumes ``str`` —
|
|
201
|
+
# ``html_url: 1`` raised AttributeError deep in the service (#2881 §3, the
|
|
202
|
+
# #2795 container-vs-value class).
|
|
203
|
+
pr_url=pr_url if isinstance(pr_url, str) else None,
|
|
204
|
+
delivery_key=delivery_key,
|
|
205
|
+
raw_event_name=event_name,
|
|
206
|
+
ignored_reason=ignored_reason,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _parse_gitlab(payload: dict[str, Any]) -> GitWebhookEnvelope:
|
|
211
|
+
object_kind = payload.get("object_kind") if isinstance(payload, dict) else None
|
|
212
|
+
attrs = payload.get("object_attributes") if isinstance(payload, dict) else None
|
|
213
|
+
attrs = attrs if isinstance(attrs, dict) else {}
|
|
214
|
+
action = attrs.get("action")
|
|
215
|
+
pr_url = attrs.get("url")
|
|
216
|
+
# GitLab's draft flag is ``work_in_progress``; newer versions also send
|
|
217
|
+
# ``draft``. Read both, exactly as the link-status parser at
|
|
218
|
+
# ``providers.GitLabProvider`` already does (#2882).
|
|
219
|
+
is_draft = bool(attrs.get("work_in_progress") or attrs.get("draft"))
|
|
220
|
+
|
|
221
|
+
event: str | None = None
|
|
222
|
+
ignored_reason = ""
|
|
223
|
+
if object_kind == "merge_request" and isinstance(action, str):
|
|
224
|
+
if action in _GITLAB_OPEN_ACTIONS:
|
|
225
|
+
if is_draft:
|
|
226
|
+
ignored_reason = _IGNORED_DRAFT
|
|
227
|
+
else:
|
|
228
|
+
event = GIT_EVENT_PR_OPENED
|
|
229
|
+
elif action == "merge":
|
|
230
|
+
event = GIT_EVENT_PR_MERGED
|
|
231
|
+
|
|
232
|
+
# GitLab has no delivery header — derive a stable key from the MR identity and
|
|
233
|
+
# action so a redelivery of the same state change dedups.
|
|
234
|
+
delivery_key = f"{object_kind}:{attrs.get('id')}:{action}"
|
|
235
|
+
|
|
236
|
+
return GitWebhookEnvelope(
|
|
237
|
+
provider=PROVIDER_GITLAB,
|
|
238
|
+
event=event,
|
|
239
|
+
pr_url=pr_url if isinstance(pr_url, str) else None,
|
|
240
|
+
delivery_key=delivery_key,
|
|
241
|
+
raw_event_name=str(object_kind or ""),
|
|
242
|
+
ignored_reason=ignored_reason,
|
|
243
|
+
)
|