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,453 @@
|
|
|
1
|
+
"""DRF serializers for the ``/me/credentials/`` viewset (ADR-0049 §3).
|
|
2
|
+
|
|
3
|
+
Three serializers, all built around the rule that the encrypted PAT
|
|
4
|
+
ciphertext is **never** rendered back to the client — not even to the
|
|
5
|
+
authenticated owner. The list response is intentionally a derived view
|
|
6
|
+
("does a row exist? when was it last used? when does it expire?"); read
|
|
7
|
+
of the underlying secret only happens server-side when the git-aware
|
|
8
|
+
tasks refresh endpoint (#637) needs it.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from typing import Any
|
|
14
|
+
from urllib.parse import urlparse
|
|
15
|
+
|
|
16
|
+
from django.core.exceptions import ValidationError as DjangoValidationError
|
|
17
|
+
from django.core.validators import URLValidator
|
|
18
|
+
from rest_framework import serializers
|
|
19
|
+
|
|
20
|
+
from .models import IntegrationCredential, TaskLink
|
|
21
|
+
from .registry import TASK_LINK_PROVIDERS
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CredentialSummarySerializer(serializers.Serializer[Any]):
|
|
25
|
+
"""One row per provider in the list response.
|
|
26
|
+
|
|
27
|
+
Surfaces:
|
|
28
|
+
- ``provider`` (machine key) and ``name`` (human label) for the UI header
|
|
29
|
+
- ``exists`` — drives the Connected / Not connected pill
|
|
30
|
+
- ``base_url`` for self-hosted users
|
|
31
|
+
- ``created_at`` / ``updated_at`` / ``last_used_at`` for the audit trail
|
|
32
|
+
- ``expires_at`` for the "expires in X days" hint
|
|
33
|
+
- ``requires_credential`` so the UI can hide the Connect button for the
|
|
34
|
+
``generic`` provider when no PAT is needed
|
|
35
|
+
|
|
36
|
+
Notably absent: any field derived from ``secret_ciphertext``.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
provider = serializers.CharField()
|
|
40
|
+
name = serializers.CharField()
|
|
41
|
+
exists = serializers.BooleanField()
|
|
42
|
+
base_url = serializers.CharField(allow_blank=True, required=False)
|
|
43
|
+
created_at = serializers.DateTimeField(allow_null=True)
|
|
44
|
+
updated_at = serializers.DateTimeField(allow_null=True)
|
|
45
|
+
last_used_at = serializers.DateTimeField(allow_null=True)
|
|
46
|
+
expires_at = serializers.DateTimeField(allow_null=True)
|
|
47
|
+
requires_credential = serializers.BooleanField()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class CredentialUpsertSerializer(serializers.Serializer[Any]):
|
|
51
|
+
"""Payload for ``POST /me/credentials/{provider}/`` (connect or rotate).
|
|
52
|
+
|
|
53
|
+
Validation rules:
|
|
54
|
+
- ``secret`` is required and non-empty (the encryption helper refuses
|
|
55
|
+
empty plaintext anyway; rejecting here gives a clean 400).
|
|
56
|
+
- ``secret`` is write-only — never echoed back.
|
|
57
|
+
- ``base_url`` is optional. When present it must be an ``http://`` or
|
|
58
|
+
``https://`` URL; ``file://`` / ``javascript:`` / ``gopher://`` /
|
|
59
|
+
similar schemes are rejected here. This serializer is the cheap first
|
|
60
|
+
line of defense; the resolver-level SSRF guard (block RFC1918, link-local,
|
|
61
|
+
cloud metadata at connect time) lands with #677's PAT verification in
|
|
62
|
+
``integrations.http`` and is reused by #637's git-link refresh.
|
|
63
|
+
- ``expires_at`` is optional and informational only.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
secret = serializers.CharField(
|
|
67
|
+
write_only=True, min_length=1, max_length=4096, trim_whitespace=False
|
|
68
|
+
)
|
|
69
|
+
base_url = serializers.CharField(required=False, allow_blank=True, max_length=512)
|
|
70
|
+
expires_at = serializers.DateTimeField(required=False, allow_null=True)
|
|
71
|
+
|
|
72
|
+
def validate_secret(self, value: str) -> str:
|
|
73
|
+
# trim_whitespace=False above lets us catch the all-whitespace case
|
|
74
|
+
# explicitly so the error message is clear.
|
|
75
|
+
if not value.strip():
|
|
76
|
+
raise serializers.ValidationError("Secret must not be blank.")
|
|
77
|
+
return value
|
|
78
|
+
|
|
79
|
+
def validate_base_url(self, value: str) -> str:
|
|
80
|
+
if not value:
|
|
81
|
+
return value
|
|
82
|
+
if "://" not in value:
|
|
83
|
+
raise serializers.ValidationError(
|
|
84
|
+
"Host URL must include a scheme (https://example.com)."
|
|
85
|
+
)
|
|
86
|
+
parsed = urlparse(value)
|
|
87
|
+
if parsed.scheme.lower() not in ("http", "https"):
|
|
88
|
+
raise serializers.ValidationError(
|
|
89
|
+
f"Host URL scheme {parsed.scheme.lower()!r} is not allowed. Use http or https."
|
|
90
|
+
)
|
|
91
|
+
# A host URL is a base — a path (self-hosted GitLab relative root, e.g.
|
|
92
|
+
# https://example.com/gitlab) is fine, but a query string or fragment is
|
|
93
|
+
# never meaningful and would corrupt the constructed ``…/api/v4/user``
|
|
94
|
+
# verify URL (e.g. https://host#frag/api/v4/user). Reject them here.
|
|
95
|
+
if parsed.query or parsed.fragment:
|
|
96
|
+
raise serializers.ValidationError(
|
|
97
|
+
"Host URL must not contain a query string or fragment."
|
|
98
|
+
)
|
|
99
|
+
return value
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class CredentialVerificationErrorSerializer(serializers.Serializer[Any]):
|
|
103
|
+
"""422 body when live PAT verification fails (#677).
|
|
104
|
+
|
|
105
|
+
Documents the shape the Connected Accounts page reads on a rejected
|
|
106
|
+
connect/rotate: a human ``detail``, the stable ``code``
|
|
107
|
+
``provider_verification_failed``, and a machine-readable ``reason`` the
|
|
108
|
+
client can branch on (``invalid_token``, ``provider_unreachable``,
|
|
109
|
+
``provider_timeout``, ``blocked_host``).
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
detail = serializers.CharField()
|
|
113
|
+
code = serializers.CharField()
|
|
114
|
+
reason = serializers.CharField(allow_null=True)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class TaskLinkSerializer(serializers.ModelSerializer[TaskLink]):
|
|
118
|
+
"""A git/PM link on a task (ADR-0049 §3, #637; redesign #970).
|
|
119
|
+
|
|
120
|
+
Client-writable: ``url``, ``custom_title``, ``labels``, ``display_order``.
|
|
121
|
+
``provider`` is resolved server-side from the URL + the user's connected
|
|
122
|
+
hosts, and ``status``/``title``/``fetched_at`` are populated by the refresh
|
|
123
|
+
endpoint — never trusted from the client. ``server_version`` is exposed so
|
|
124
|
+
offline clients can reconcile against the sync delta.
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
# Declared as a plain CharField (not the auto ModelSerializer URLField) so a
|
|
128
|
+
# bare host like "github.com/acme/api" survives field validation and reaches
|
|
129
|
+
# ``validate_url``, which prepends the scheme before validating (#970). A
|
|
130
|
+
# strict URLField would 400 it before normalization could run.
|
|
131
|
+
url = serializers.CharField(max_length=2048, trim_whitespace=True)
|
|
132
|
+
# Optional free-text tags; entries are trimmed/de-duped/capped in
|
|
133
|
+
# ``validate_labels``. ``allow_blank`` on the child so a stray "" is dropped
|
|
134
|
+
# rather than 400-ing the whole request.
|
|
135
|
+
labels = serializers.ListField(
|
|
136
|
+
child=serializers.CharField(max_length=40, allow_blank=True),
|
|
137
|
+
required=False,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
class Meta:
|
|
141
|
+
model = TaskLink
|
|
142
|
+
fields = [
|
|
143
|
+
"id",
|
|
144
|
+
"url",
|
|
145
|
+
"provider",
|
|
146
|
+
"title",
|
|
147
|
+
"custom_title",
|
|
148
|
+
"labels",
|
|
149
|
+
"status",
|
|
150
|
+
"fetched_at",
|
|
151
|
+
# Cloud-file preview cache (#571, ADR-0163) — server-owned, populated
|
|
152
|
+
# by a file provider's OpenGraph unfurl on refresh; read-only.
|
|
153
|
+
"description",
|
|
154
|
+
"thumbnail_url",
|
|
155
|
+
"preview_type",
|
|
156
|
+
"display_order",
|
|
157
|
+
"server_version",
|
|
158
|
+
]
|
|
159
|
+
read_only_fields = [
|
|
160
|
+
"id",
|
|
161
|
+
"provider",
|
|
162
|
+
"title",
|
|
163
|
+
"status",
|
|
164
|
+
"fetched_at",
|
|
165
|
+
"description",
|
|
166
|
+
"thumbnail_url",
|
|
167
|
+
"preview_type",
|
|
168
|
+
"server_version",
|
|
169
|
+
]
|
|
170
|
+
|
|
171
|
+
def validate_url(self, value: str) -> str:
|
|
172
|
+
"""Normalize a scheme-less URL to ``https://`` then validate (#970).
|
|
173
|
+
|
|
174
|
+
Users routinely paste a bare ``github.com/acme/api/pull/5`` — prepend
|
|
175
|
+
``https://`` rather than reject it. Anything with an explicit scheme is
|
|
176
|
+
left as-is so a deliberate ``ftp://`` / ``javascript:`` is still caught
|
|
177
|
+
by the http(s)-only ``URLValidator``. The resolver-level SSRF guard runs
|
|
178
|
+
separately at refresh time when the URL is actually fetched.
|
|
179
|
+
"""
|
|
180
|
+
raw = (value or "").strip()
|
|
181
|
+
if not raw:
|
|
182
|
+
raise serializers.ValidationError("Enter a URL.")
|
|
183
|
+
if "://" not in raw:
|
|
184
|
+
raw = "https://" + raw.lstrip("/")
|
|
185
|
+
try:
|
|
186
|
+
URLValidator(schemes=["http", "https"])(raw)
|
|
187
|
+
except DjangoValidationError:
|
|
188
|
+
raise serializers.ValidationError("Enter a valid http(s) web address.") from None
|
|
189
|
+
return raw
|
|
190
|
+
|
|
191
|
+
def validate_labels(self, value: list[str]) -> list[str]:
|
|
192
|
+
"""Trim, drop blanks, de-dupe (case-insensitive), and cap at 12 (#970)."""
|
|
193
|
+
cleaned: list[str] = []
|
|
194
|
+
seen: set[str] = set()
|
|
195
|
+
for raw in value:
|
|
196
|
+
label = (raw or "").strip()
|
|
197
|
+
if not label:
|
|
198
|
+
continue
|
|
199
|
+
key = label.casefold()
|
|
200
|
+
if key in seen:
|
|
201
|
+
continue
|
|
202
|
+
seen.add(key)
|
|
203
|
+
cleaned.append(label)
|
|
204
|
+
if len(cleaned) > 12:
|
|
205
|
+
raise serializers.ValidationError("A link can have at most 12 labels.")
|
|
206
|
+
return cleaned
|
|
207
|
+
|
|
208
|
+
def validate_custom_title(self, value: str) -> str:
|
|
209
|
+
return (value or "").strip()
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
class TaskLinkCredentialRequiredSerializer(serializers.Serializer[Any]):
|
|
213
|
+
"""422 body when refresh needs a PAT the caller hasn't connected (#637).
|
|
214
|
+
|
|
215
|
+
Lets the task-detail UI swap the status badge for a "Connect {provider}"
|
|
216
|
+
affordance pointing at ``/me/settings/connected-accounts`` instead of
|
|
217
|
+
leaving the link stuck at ``unknown``.
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
detail = serializers.CharField()
|
|
221
|
+
code = serializers.CharField()
|
|
222
|
+
provider = serializers.CharField()
|
|
223
|
+
requires_credential = serializers.BooleanField()
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def serialize_credential_summaries(
|
|
227
|
+
user: Any,
|
|
228
|
+
queryset: list[IntegrationCredential],
|
|
229
|
+
) -> list[dict[str, Any]]:
|
|
230
|
+
"""Build the list-response payload for ``GET /me/credentials/``.
|
|
231
|
+
|
|
232
|
+
Iterates the registered providers (so the response always includes one
|
|
233
|
+
entry per provider, whether the user has a row or not) and joins the
|
|
234
|
+
user's existing rows by ``provider`` key. This is the shape the
|
|
235
|
+
Connected Accounts page consumes — it never has to reason about
|
|
236
|
+
"missing rows" vs "no providers registered."
|
|
237
|
+
"""
|
|
238
|
+
rows_by_provider: dict[str, IntegrationCredential] = {row.provider: row for row in queryset}
|
|
239
|
+
out: list[dict[str, Any]] = []
|
|
240
|
+
for key in TASK_LINK_PROVIDERS:
|
|
241
|
+
handler = TASK_LINK_PROVIDERS.get(key)
|
|
242
|
+
if handler is None: # pragma: no cover — keys() iterates known
|
|
243
|
+
continue
|
|
244
|
+
# Skip providers that never store a credential (cloud-file preview
|
|
245
|
+
# providers, #571) — the Connected Accounts page is a PAT manager, and
|
|
246
|
+
# an account with nothing to connect would only confuse.
|
|
247
|
+
if not getattr(handler, "connectable", True):
|
|
248
|
+
continue
|
|
249
|
+
row = rows_by_provider.get(key)
|
|
250
|
+
out.append(
|
|
251
|
+
{
|
|
252
|
+
"provider": key,
|
|
253
|
+
"name": getattr(handler, "label", key),
|
|
254
|
+
"exists": row is not None,
|
|
255
|
+
"base_url": row.base_url if row else "",
|
|
256
|
+
"created_at": row.created_at if row else None,
|
|
257
|
+
"updated_at": row.updated_at if row else None,
|
|
258
|
+
"last_used_at": row.last_used_at if row else None,
|
|
259
|
+
"expires_at": row.expires_at if row else None,
|
|
260
|
+
"requires_credential": getattr(handler, "requires_credential", True),
|
|
261
|
+
}
|
|
262
|
+
)
|
|
263
|
+
return out
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
#: Outcomes a **verified** delivery can produce (#2882). Published as an ``enum`` on
|
|
267
|
+
#: ``GitAutomationConfig.last_delivery_outcome`` so an SDK consumer can discover the
|
|
268
|
+
#: vocabulary the web client renders as user-facing copy. Kept as a module constant,
|
|
269
|
+
#: not re-typed inline, because the receiver and the schema must not drift.
|
|
270
|
+
GIT_DELIVERY_OUTCOMES = (
|
|
271
|
+
"malformed_payload",
|
|
272
|
+
"ignored",
|
|
273
|
+
"draft",
|
|
274
|
+
"duplicate",
|
|
275
|
+
"no_url",
|
|
276
|
+
"no_link",
|
|
277
|
+
"noop_forward_only",
|
|
278
|
+
"opened_review",
|
|
279
|
+
"merged_complete",
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
#: Outcomes a **refused** (pre-verification) delivery can produce. ``no_automation``
|
|
283
|
+
#: is absent on purpose: that refusal has no ``BoardAutomation`` row to write to, so
|
|
284
|
+
#: it can only ever reach the receiver log — publishing it as a readable value would
|
|
285
|
+
#: be advertising a field state that cannot occur.
|
|
286
|
+
GIT_REFUSAL_OUTCOMES = (
|
|
287
|
+
"automation_disabled",
|
|
288
|
+
"no_secret",
|
|
289
|
+
"unknown_provider",
|
|
290
|
+
"secret_unreadable",
|
|
291
|
+
"bad_signature",
|
|
292
|
+
"project_archived",
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
class GitAutomationConfigSerializer(serializers.Serializer[Any]):
|
|
297
|
+
"""Read view of a project's Git-event card automation (#329, ADR-0158).
|
|
298
|
+
|
|
299
|
+
Never renders the secret — only whether one is set. ``webhook_url`` is the
|
|
300
|
+
per-project endpoint the admin pastes into GitHub/GitLab; the secret travels
|
|
301
|
+
in the provider's signature header, never in the URL.
|
|
302
|
+
"""
|
|
303
|
+
|
|
304
|
+
enabled = serializers.BooleanField()
|
|
305
|
+
secret_set = serializers.BooleanField()
|
|
306
|
+
webhook_url = serializers.CharField()
|
|
307
|
+
configured_by = serializers.UUIDField(allow_null=True)
|
|
308
|
+
secret_set_at = serializers.DateTimeField(allow_null=True)
|
|
309
|
+
updated_at = serializers.DateTimeField(allow_null=True)
|
|
310
|
+
# Delivery diagnostics (#2882). Before these, a webhook that failed or matched
|
|
311
|
+
# nothing was invisible on every surface an operator can reach: the receiver
|
|
312
|
+
# logged nothing, every non-auth outcome was a 200 so the provider showed a green
|
|
313
|
+
# check, and the settings card had no error state at all. These are what the
|
|
314
|
+
# "Last delivery" and "Last refused delivery" rows on that card read.
|
|
315
|
+
#
|
|
316
|
+
# ``ChoiceField``, not ``CharField``, and not for validation — these are
|
|
317
|
+
# read-only outputs. It is so the vocabulary reaches ``openapi.json`` as an
|
|
318
|
+
# ``enum``. The same MR gives the receiver's own ``reason`` a documented enum, and
|
|
319
|
+
# publishing its superset as a bare ``string`` here would leave an SDK consumer no
|
|
320
|
+
# way to discover the values, while the web client maps every one of them to
|
|
321
|
+
# user-facing copy. A token the server can emit and a client cannot look up is the
|
|
322
|
+
# declared-vs-actual gap this issue is about.
|
|
323
|
+
last_delivery_at = serializers.DateTimeField(allow_null=True)
|
|
324
|
+
last_delivery_outcome = serializers.ChoiceField(
|
|
325
|
+
choices=GIT_DELIVERY_OUTCOMES,
|
|
326
|
+
allow_blank=True,
|
|
327
|
+
help_text=(
|
|
328
|
+
"Outcome of the last delivery whose signature VERIFIED. Empty until one "
|
|
329
|
+
"arrives. Trustworthy: the caller proved it holds the project's secret."
|
|
330
|
+
),
|
|
331
|
+
)
|
|
332
|
+
last_delivery_provider = serializers.CharField(allow_blank=True)
|
|
333
|
+
# Deliberately separate from the fields above — see BoardAutomation. A refusal is
|
|
334
|
+
# recorded before verification, so an unauthenticated caller holding the project
|
|
335
|
+
# UUID can write here at will; keeping it out of ``last_delivery_*`` is what stops
|
|
336
|
+
# that caller erasing a genuine outcome or provoking a needless secret rotation.
|
|
337
|
+
last_refusal_at = serializers.DateTimeField(allow_null=True)
|
|
338
|
+
last_refusal_outcome = serializers.ChoiceField(
|
|
339
|
+
choices=GIT_REFUSAL_OUTCOMES,
|
|
340
|
+
allow_blank=True,
|
|
341
|
+
help_text=(
|
|
342
|
+
"Why the last REFUSED delivery was refused. The caller saw only an opaque "
|
|
343
|
+
"404. Diagnostic, not evidence: the endpoint is public, so this may record "
|
|
344
|
+
"unauthenticated traffic rather than your provider."
|
|
345
|
+
),
|
|
346
|
+
)
|
|
347
|
+
last_refusal_provider = serializers.CharField(allow_blank=True)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
class GitAutomationUpdateSerializer(serializers.Serializer[Any]):
|
|
351
|
+
"""Write payload for toggling Git-event automation (project-admin only)."""
|
|
352
|
+
|
|
353
|
+
enabled = serializers.BooleanField()
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
class GitAutomationSecretSerializer(serializers.Serializer[Any]):
|
|
357
|
+
"""One-time rotate-secret response (``201``) for ``.../rotate-secret/``.
|
|
358
|
+
|
|
359
|
+
Declared so the published schema stops describing this endpoint as
|
|
360
|
+
"200: No response body" — it returns **201** with a plaintext secret that is
|
|
361
|
+
never retrievable again, which is precisely the shape an integrator has to know
|
|
362
|
+
in advance (#2882).
|
|
363
|
+
"""
|
|
364
|
+
|
|
365
|
+
secret = serializers.CharField()
|
|
366
|
+
webhook_url = serializers.CharField()
|
|
367
|
+
secret_set_at = serializers.DateTimeField(allow_null=True)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
#: Raw OpenAPI schema for the inbound receiver's **request** body (#3364).
|
|
371
|
+
#:
|
|
372
|
+
#: The receiver published no ``requestBody`` at all (``request=None``), which told a
|
|
373
|
+
#: generated client the endpoint accepts nothing — while the handler reads
|
|
374
|
+
#: ``request.data`` and refuses a non-object with a 400. There is no serializer to
|
|
375
|
+
#: name here and there should not be: the body is the *provider's* pull/merge-request
|
|
376
|
+
#: event, whose shape GitHub and GitLab own and change without asking us. Declaring a
|
|
377
|
+
#: fixed schema would publish a contract we do not control and cannot keep true.
|
|
378
|
+
#:
|
|
379
|
+
#: So this declares exactly what the receiver actually requires — a JSON **object**,
|
|
380
|
+
#: any keys — and says in prose where the real shape is defined. ``parse_envelope``
|
|
381
|
+
#: reads a handful of provider-specific keys out of it and ignores the rest, which is
|
|
382
|
+
#: why "an object" is not a cop-out but the honest ceiling on what we can promise.
|
|
383
|
+
GIT_WEBHOOK_PAYLOAD_SCHEMA: dict[str, Any] = {
|
|
384
|
+
"type": "object",
|
|
385
|
+
"additionalProperties": True,
|
|
386
|
+
"description": (
|
|
387
|
+
"The provider's own webhook event payload, forwarded verbatim — GitHub's "
|
|
388
|
+
"`pull_request` event or GitLab's `merge_request` event. Its shape is defined "
|
|
389
|
+
"by the provider, not by TruePPM, so it is typed here only as far as this "
|
|
390
|
+
"receiver actually constrains it: a JSON object. A non-object body is refused "
|
|
391
|
+
"with a 400 once the signature has verified. The signature is computed over "
|
|
392
|
+
"these exact bytes, so the body must be sent unmodified."
|
|
393
|
+
),
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
#: Raw OpenAPI schema for the inbound receiver's ``200`` body (#2882).
|
|
398
|
+
#:
|
|
399
|
+
#: The published schema declared all four Git endpoints as "200: No response body",
|
|
400
|
+
#: while the receiver actually returns four distinct shapes and a ``reason`` enum that
|
|
401
|
+
#: was discoverable only by reading Python. Expressed as a raw schema rather than a
|
|
402
|
+
#: ``Serializer`` for one blunt reason: the body carries a ``from`` key, and ``from``
|
|
403
|
+
#: is a Python keyword, so it cannot be a serializer class attribute. Renaming the
|
|
404
|
+
#: wire field to suit the tooling would be the tail wagging the dog.
|
|
405
|
+
GIT_WEBHOOK_RESULT_SCHEMA: dict[str, Any] = {
|
|
406
|
+
"type": "object",
|
|
407
|
+
"properties": {
|
|
408
|
+
"matched": {
|
|
409
|
+
"type": "boolean",
|
|
410
|
+
"description": "A task in this project is linked to the PR/MR in the payload.",
|
|
411
|
+
},
|
|
412
|
+
"moved": {"type": "boolean", "description": "The linked card's status changed."},
|
|
413
|
+
"task": {
|
|
414
|
+
"type": "string",
|
|
415
|
+
"format": "uuid",
|
|
416
|
+
"nullable": True,
|
|
417
|
+
"description": "The matched task, when there was one.",
|
|
418
|
+
},
|
|
419
|
+
"from": {"type": "string", "nullable": True, "description": "Board status before."},
|
|
420
|
+
"to": {"type": "string", "nullable": True, "description": "Board status after."},
|
|
421
|
+
"ignored": {
|
|
422
|
+
"type": "string",
|
|
423
|
+
"description": (
|
|
424
|
+
"The provider's own event name, present when the event is not one this "
|
|
425
|
+
"receiver acts on."
|
|
426
|
+
),
|
|
427
|
+
},
|
|
428
|
+
"reason": {
|
|
429
|
+
"type": "string",
|
|
430
|
+
"enum": [
|
|
431
|
+
"draft",
|
|
432
|
+
"duplicate",
|
|
433
|
+
"no_url",
|
|
434
|
+
"no_link",
|
|
435
|
+
"noop_forward_only",
|
|
436
|
+
"opened_review",
|
|
437
|
+
"merged_complete",
|
|
438
|
+
],
|
|
439
|
+
"description": (
|
|
440
|
+
"draft: a draft/WIP pull/merge request opened — deliberately does not "
|
|
441
|
+
"promote the card, the promotion happens when it is marked ready for "
|
|
442
|
+
"review. duplicate: a provider redelivery of an event already "
|
|
443
|
+
"processed. no_url: the payload carried no pull/merge request URL. "
|
|
444
|
+
"no_link: no task in this project has a link pointing at this PR/MR, so "
|
|
445
|
+
"there is no card to move — this is the most common reason a correctly "
|
|
446
|
+
"configured webhook moves nothing. noop_forward_only: the card is "
|
|
447
|
+
"already at or past the target status. opened_review / merged_complete: "
|
|
448
|
+
"a card moved."
|
|
449
|
+
),
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
"required": ["matched", "moved"],
|
|
453
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"""Service layer for user-scoped external task sync (ADR-0097 §4).
|
|
2
|
+
|
|
3
|
+
``enqueue_external_sync`` is the single entry point that queues a read-only pull
|
|
4
|
+
of one ``(user, source)`` connection. It follows the canonical transactional
|
|
5
|
+
outbox shape (ADR-0017/0019): write an ``ExternalSyncRequest`` row inside a
|
|
6
|
+
transaction, then a best-effort ``transaction.on_commit`` dispatch of the
|
|
7
|
+
``external_sync`` Celery task. If the broker is down the row stays ``PENDING``
|
|
8
|
+
and the 300 s ``drain-external-sync`` Beat task recovers it.
|
|
9
|
+
|
|
10
|
+
Never call ``external_sync.delay()`` directly from a view — the outbox row is
|
|
11
|
+
what makes the pull survive a broker outage (ADR-0097 §Durable Execution #4).
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import logging
|
|
17
|
+
import uuid
|
|
18
|
+
|
|
19
|
+
from django.db import IntegrityError, transaction
|
|
20
|
+
from django.utils import timezone
|
|
21
|
+
|
|
22
|
+
from .external_sources import EXTERNAL_TASK_SOURCES
|
|
23
|
+
from .models import (
|
|
24
|
+
ExternalSyncRequest,
|
|
25
|
+
ExternalSyncRequestReason,
|
|
26
|
+
ExternalSyncRequestStatus,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
# Minimum spacing between *manual* refreshes of one connection (ADR-0097
|
|
32
|
+
# §Resolution #5 "per-user cooldown ≥60 s"). The partial-unique constraint
|
|
33
|
+
# already coalesces a burst onto one in-flight pull; this outer bound also
|
|
34
|
+
# rate-limits back-to-back refreshes *after* a pull completes, so a user
|
|
35
|
+
# hammering the button cannot walk Jira's rate budget. The opt-in poll and the
|
|
36
|
+
# on-open refresh are exempt — they are already low-frequency by construction.
|
|
37
|
+
MANUAL_SYNC_COOLDOWN_SECONDS = 60
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class SyncCooldownActive(Exception):
|
|
41
|
+
"""A manual refresh was requested inside the per-connection cooldown window.
|
|
42
|
+
|
|
43
|
+
Carries ``retry_after`` (seconds) so the view can answer ``429`` with a
|
|
44
|
+
``Retry-After`` header rather than silently swallowing the request.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(self, retry_after: int) -> None:
|
|
48
|
+
self.retry_after = retry_after
|
|
49
|
+
super().__init__(f"Sync cooldown active — retry in {retry_after}s")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def enqueue_external_sync(
|
|
53
|
+
user_id: int,
|
|
54
|
+
source: str,
|
|
55
|
+
*,
|
|
56
|
+
reason: ExternalSyncRequestReason = ExternalSyncRequestReason.MANUAL,
|
|
57
|
+
) -> ExternalSyncRequest | None:
|
|
58
|
+
"""Queue a read-only pull of one external-source connection (outbox + dispatch).
|
|
59
|
+
|
|
60
|
+
Idempotent: if a ``PENDING`` row already exists for ``(user, source)`` it is
|
|
61
|
+
adopted (coalescing a burst of triggers onto one pull) rather than stacking a
|
|
62
|
+
second Jira fetch — mirrors ``scheduling.enqueue_recalculate``.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
user_id: Owner of the connection (rows are strictly per-user).
|
|
66
|
+
source: An ``EXTERNAL_TASK_SOURCES`` key (e.g. ``"jira"``).
|
|
67
|
+
reason: Why the pull was queued — forensics only.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
The queued (or adopted) ``ExternalSyncRequest``, or ``None`` if a race
|
|
71
|
+
transitioned the pending row out from under the adopt path.
|
|
72
|
+
|
|
73
|
+
Raises:
|
|
74
|
+
ValueError: ``source`` is not a registered external task source.
|
|
75
|
+
SyncCooldownActive: ``reason`` is ``MANUAL`` and the last request for this
|
|
76
|
+
connection is inside :data:`MANUAL_SYNC_COOLDOWN_SECONDS`.
|
|
77
|
+
"""
|
|
78
|
+
if EXTERNAL_TASK_SOURCES.get(source) is None:
|
|
79
|
+
raise ValueError(f"Unknown external task source {source!r}.")
|
|
80
|
+
|
|
81
|
+
now = timezone.now()
|
|
82
|
+
|
|
83
|
+
# Manual-refresh cooldown (ADR-0097 §Resolution #5). Measured from the most
|
|
84
|
+
# recent request's ``requested_at`` regardless of its terminal state, so a
|
|
85
|
+
# completed pull still blocks an immediate re-trigger. Poll / on-open reasons
|
|
86
|
+
# skip this — they are already spaced by their Beat cadence.
|
|
87
|
+
if reason == ExternalSyncRequestReason.MANUAL:
|
|
88
|
+
last = (
|
|
89
|
+
ExternalSyncRequest.objects.filter(user_id=user_id, source=source)
|
|
90
|
+
.order_by("-requested_at")
|
|
91
|
+
.first()
|
|
92
|
+
)
|
|
93
|
+
if last is not None:
|
|
94
|
+
elapsed = (now - last.requested_at).total_seconds()
|
|
95
|
+
if elapsed < MANUAL_SYNC_COOLDOWN_SECONDS:
|
|
96
|
+
raise SyncCooldownActive(
|
|
97
|
+
retry_after=max(1, int(MANUAL_SYNC_COOLDOWN_SECONDS - elapsed))
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
req: ExternalSyncRequest
|
|
101
|
+
try:
|
|
102
|
+
with transaction.atomic():
|
|
103
|
+
req = ExternalSyncRequest.objects.create(user_id=user_id, source=source, reason=reason)
|
|
104
|
+
except IntegrityError:
|
|
105
|
+
# A PENDING row already exists (partial-unique constraint). Adopt it — it
|
|
106
|
+
# may be stranded (its own dispatch failed on a broker blip) and every
|
|
107
|
+
# subsequent trigger would otherwise pile up behind it. Re-dispatching now
|
|
108
|
+
# collapses them into one pull with the latest Jira data.
|
|
109
|
+
existing = ExternalSyncRequest.objects.filter(
|
|
110
|
+
user_id=user_id,
|
|
111
|
+
source=source,
|
|
112
|
+
status=ExternalSyncRequestStatus.PENDING,
|
|
113
|
+
).first()
|
|
114
|
+
if existing is None:
|
|
115
|
+
# Race: the row left PENDING between the insert and this lookup — the
|
|
116
|
+
# other writer already dispatched it, so there is nothing to do.
|
|
117
|
+
return None
|
|
118
|
+
req = existing
|
|
119
|
+
|
|
120
|
+
# An adopted row may be a rate-limited pull sitting out the clock the
|
|
121
|
+
# source itself gave us (``_requeue_rate_limited`` puts it back in PENDING
|
|
122
|
+
# with ``next_attempt_at``). Dispatching it now would re-hit the source
|
|
123
|
+
# inside its own ``Retry-After`` and earn another 429 — the ADR-0097 §4
|
|
124
|
+
# "backoff that respects Retry-After" promise, broken by the trigger rather
|
|
125
|
+
# than by the backoff.
|
|
126
|
+
#
|
|
127
|
+
# This became routine with the background-poll opt-in (#3104): the poll
|
|
128
|
+
# ticks every 15 minutes and ``_RATE_LIMIT_MAX_BACKOFF`` is also 15
|
|
129
|
+
# minutes, so an always-on connection reliably ticks inside an active
|
|
130
|
+
# backoff window with nobody touching the UI. ``drain_external_sync``
|
|
131
|
+
# already honors the same clock and re-dispatches on its own 300 s cadence,
|
|
132
|
+
# so leaving the row alone loses nothing — the pull still happens, just
|
|
133
|
+
# when the source said it could.
|
|
134
|
+
if req.next_attempt_at is not None and req.next_attempt_at > now:
|
|
135
|
+
return req
|
|
136
|
+
|
|
137
|
+
_dispatch_on_commit(req.id)
|
|
138
|
+
return req
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _dispatch_on_commit(request_id: uuid.UUID) -> None:
|
|
142
|
+
"""Register a best-effort ``external_sync`` dispatch for after commit.
|
|
143
|
+
|
|
144
|
+
The dispatch is deferred to ``transaction.on_commit`` so the outbox row is
|
|
145
|
+
durably committed before the worker reads it. A broker error is swallowed —
|
|
146
|
+
the row stays ``PENDING`` and ``drain_external_sync`` re-dispatches it.
|
|
147
|
+
"""
|
|
148
|
+
|
|
149
|
+
def _dispatch() -> None:
|
|
150
|
+
from .tasks import external_sync
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
result = external_sync.delay(str(request_id))
|
|
154
|
+
except Exception:
|
|
155
|
+
logger.exception(
|
|
156
|
+
"enqueue_external_sync: immediate dispatch failed for request %s "
|
|
157
|
+
"— drain_external_sync will retry",
|
|
158
|
+
request_id,
|
|
159
|
+
)
|
|
160
|
+
return
|
|
161
|
+
# Flip to DISPATCHED only if still PENDING, so a concurrent drain that
|
|
162
|
+
# already dispatched this row is not clobbered.
|
|
163
|
+
ExternalSyncRequest.objects.filter(
|
|
164
|
+
id=request_id, status=ExternalSyncRequestStatus.PENDING
|
|
165
|
+
).update(
|
|
166
|
+
status=ExternalSyncRequestStatus.DISPATCHED,
|
|
167
|
+
celery_task_id=result.id,
|
|
168
|
+
dispatched_at=timezone.now(),
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
transaction.on_commit(_dispatch)
|