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,71 @@
|
|
|
1
|
+
"""Initial migration for the idempotency app (ADR-0170)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
|
|
7
|
+
import django.db.models.deletion
|
|
8
|
+
from django.conf import settings
|
|
9
|
+
from django.db import migrations, models
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Migration(migrations.Migration):
|
|
13
|
+
initial = True
|
|
14
|
+
|
|
15
|
+
dependencies = [
|
|
16
|
+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
operations = [
|
|
20
|
+
migrations.CreateModel(
|
|
21
|
+
name="IdempotencyKey",
|
|
22
|
+
fields=[
|
|
23
|
+
(
|
|
24
|
+
"id",
|
|
25
|
+
models.UUIDField(
|
|
26
|
+
default=uuid.uuid4,
|
|
27
|
+
editable=False,
|
|
28
|
+
primary_key=True,
|
|
29
|
+
serialize=False,
|
|
30
|
+
),
|
|
31
|
+
),
|
|
32
|
+
("key", models.CharField(max_length=255)),
|
|
33
|
+
("method", models.CharField(max_length=8)),
|
|
34
|
+
("path", models.CharField(max_length=512)),
|
|
35
|
+
("request_hash", models.CharField(max_length=64)),
|
|
36
|
+
(
|
|
37
|
+
"status",
|
|
38
|
+
models.CharField(
|
|
39
|
+
choices=[
|
|
40
|
+
("processing", "Processing"),
|
|
41
|
+
("completed", "Completed"),
|
|
42
|
+
],
|
|
43
|
+
default="processing",
|
|
44
|
+
max_length=10,
|
|
45
|
+
),
|
|
46
|
+
),
|
|
47
|
+
("response_status", models.SmallIntegerField(blank=True, null=True)),
|
|
48
|
+
("response_body", models.JSONField(blank=True, null=True)),
|
|
49
|
+
("response_headers", models.JSONField(blank=True, null=True)),
|
|
50
|
+
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
51
|
+
(
|
|
52
|
+
"user",
|
|
53
|
+
models.ForeignKey(
|
|
54
|
+
db_index=False,
|
|
55
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
56
|
+
related_name="idempotency_keys",
|
|
57
|
+
to=settings.AUTH_USER_MODEL,
|
|
58
|
+
),
|
|
59
|
+
),
|
|
60
|
+
],
|
|
61
|
+
options={
|
|
62
|
+
"indexes": [models.Index(fields=["created_at"], name="idempotency_created_idx")],
|
|
63
|
+
"constraints": [
|
|
64
|
+
models.UniqueConstraint(
|
|
65
|
+
fields=["user", "key"],
|
|
66
|
+
name="idempotency_key_unique_per_user",
|
|
67
|
+
)
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
),
|
|
71
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"""DRF dispatch-layer idempotency mixin (ADR-0170).
|
|
2
|
+
|
|
3
|
+
The hook lives at the DRF dispatch layer rather than in Django middleware because
|
|
4
|
+
``ATOMIC_REQUESTS = True``: the per-request transaction wraps only the view call, so a
|
|
5
|
+
Django ``process_response`` middleware runs *outside* it and cannot store the response
|
|
6
|
+
row atomically with the mutation. ``initial()`` and ``finalize_response()`` both run
|
|
7
|
+
inside that transaction and after authentication, which is exactly what we need.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import hashlib
|
|
13
|
+
import json
|
|
14
|
+
import logging
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
from django.conf import settings
|
|
18
|
+
from django.db import IntegrityError, connection, transaction
|
|
19
|
+
from rest_framework.exceptions import APIException, ValidationError
|
|
20
|
+
from rest_framework.request import Request
|
|
21
|
+
from rest_framework.response import Response
|
|
22
|
+
|
|
23
|
+
from trueppm_api.apps.idempotency.models import IdempotencyKey
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
UNSAFE_METHODS = frozenset({"POST", "PUT", "PATCH", "DELETE"})
|
|
28
|
+
HEADER = "Idempotency-Key"
|
|
29
|
+
# Headers replayed verbatim on a cache hit. Kept minimal on purpose.
|
|
30
|
+
SAFELISTED_RESPONSE_HEADERS = ("Location",)
|
|
31
|
+
DEFAULT_MAX_BODY_BYTES = 1 * 1024 * 1024 # 1 MiB
|
|
32
|
+
MAX_KEY_LENGTH = 255 # matches IdempotencyKey.key max_length
|
|
33
|
+
|
|
34
|
+
# Sentinel: response is not replayable (5xx, streaming, non-JSON, or oversized).
|
|
35
|
+
_NOT_STORABLE = object()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class IdempotencyConflict(APIException):
|
|
39
|
+
"""Raised when a key is reused with a different request hash."""
|
|
40
|
+
|
|
41
|
+
status_code = 422
|
|
42
|
+
default_detail = "Idempotency-Key was reused with a different request."
|
|
43
|
+
default_code = "idempotency_key_conflict"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class _IdempotentReplay(Exception):
|
|
47
|
+
"""Internal control-flow signal: replay a stored response instead of running the view."""
|
|
48
|
+
|
|
49
|
+
def __init__(self, row: IdempotencyKey) -> None:
|
|
50
|
+
self.row = row
|
|
51
|
+
super().__init__("idempotent replay")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class IdempotencyMixin:
|
|
55
|
+
"""Adds ``Idempotency-Key`` replay/store semantics to a DRF view.
|
|
56
|
+
|
|
57
|
+
Apply by inheriting before the DRF base (so its ``initial`` /
|
|
58
|
+
``finalize_response`` / ``handle_exception`` overrides precede ``APIView`` in the
|
|
59
|
+
MRO). Set ``idempotency_exempt = True`` to opt a view out (auth/token endpoints,
|
|
60
|
+
already-deduped outbox entrypoints, streaming/export views).
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
idempotency_exempt: bool = False
|
|
64
|
+
|
|
65
|
+
def initial(self, request: Request, *args: Any, **kwargs: Any) -> None:
|
|
66
|
+
# Run authentication/throttling/negotiation first so request.user is resolved.
|
|
67
|
+
super().initial(request, *args, **kwargs) # type: ignore[misc]
|
|
68
|
+
|
|
69
|
+
self._idempotency_row: IdempotencyKey | None = None
|
|
70
|
+
|
|
71
|
+
if self.idempotency_exempt or request.method not in UNSAFE_METHODS:
|
|
72
|
+
return
|
|
73
|
+
key = request.headers.get(HEADER)
|
|
74
|
+
if not key:
|
|
75
|
+
return # opt-in: no header is a no-op, preserving prior behavior
|
|
76
|
+
user = getattr(request, "user", None)
|
|
77
|
+
if user is None or not user.is_authenticated:
|
|
78
|
+
return
|
|
79
|
+
if len(key) > MAX_KEY_LENGTH:
|
|
80
|
+
raise ValidationError(
|
|
81
|
+
{HEADER: f"Idempotency-Key must be {MAX_KEY_LENGTH} characters or fewer."}
|
|
82
|
+
)
|
|
83
|
+
# Requests larger than the body cap aren't idempotency-protected — skip rather than
|
|
84
|
+
# buffer+hash a very large body. They run normally (no claim stored).
|
|
85
|
+
try:
|
|
86
|
+
content_length = int(request.META.get("CONTENT_LENGTH") or 0)
|
|
87
|
+
except (TypeError, ValueError):
|
|
88
|
+
content_length = 0
|
|
89
|
+
max_bytes = getattr(settings, "IDEMPOTENCY_MAX_BODY_BYTES", DEFAULT_MAX_BODY_BYTES)
|
|
90
|
+
if content_length > max_bytes:
|
|
91
|
+
return
|
|
92
|
+
|
|
93
|
+
request_hash = self._compute_request_hash(request)
|
|
94
|
+
|
|
95
|
+
existing = IdempotencyKey.objects.filter(user=user, key=key).first()
|
|
96
|
+
if existing is not None:
|
|
97
|
+
self._resolve_existing(existing, request_hash)
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
# Miss → insert a claim row inside a savepoint so a concurrent-duplicate
|
|
101
|
+
# IntegrityError can be caught without poisoning the outer request transaction.
|
|
102
|
+
try:
|
|
103
|
+
with transaction.atomic():
|
|
104
|
+
self._idempotency_row = IdempotencyKey.objects.create(
|
|
105
|
+
user=user,
|
|
106
|
+
key=key,
|
|
107
|
+
method=request.method,
|
|
108
|
+
path=request.path[:512],
|
|
109
|
+
request_hash=request_hash,
|
|
110
|
+
status=IdempotencyKey.Status.PROCESSING,
|
|
111
|
+
)
|
|
112
|
+
except IntegrityError:
|
|
113
|
+
# A concurrent request claimed (user, key) first. Its INSERT blocked ours on
|
|
114
|
+
# the unique constraint until it committed; re-read the winner and replay.
|
|
115
|
+
winner = IdempotencyKey.objects.filter(user=user, key=key).first()
|
|
116
|
+
if winner is None:
|
|
117
|
+
# Winner rolled back (its mutation failed) after releasing the lock.
|
|
118
|
+
# Proceed without idempotency rather than failing the legitimate request.
|
|
119
|
+
return
|
|
120
|
+
self._resolve_existing(winner, request_hash)
|
|
121
|
+
|
|
122
|
+
def _resolve_existing(self, row: IdempotencyKey, request_hash: str) -> None:
|
|
123
|
+
"""Replay a completed row, or raise 422 on a hash mismatch."""
|
|
124
|
+
if row.request_hash != request_hash:
|
|
125
|
+
raise IdempotencyConflict()
|
|
126
|
+
# A committed row is always COMPLETED (see model invariant); replay it.
|
|
127
|
+
raise _IdempotentReplay(row)
|
|
128
|
+
|
|
129
|
+
def handle_exception(self, exc: Exception) -> Response:
|
|
130
|
+
if isinstance(exc, _IdempotentReplay):
|
|
131
|
+
row = exc.row
|
|
132
|
+
response = Response(row.response_body, status=row.response_status)
|
|
133
|
+
# Re-apply the safelist on replay (defense in depth — never trust the stored
|
|
134
|
+
# dict to contain only safe headers).
|
|
135
|
+
for name, value in (row.response_headers or {}).items():
|
|
136
|
+
if name in SAFELISTED_RESPONSE_HEADERS:
|
|
137
|
+
response[name] = value
|
|
138
|
+
response["Idempotent-Replay"] = "true"
|
|
139
|
+
return response
|
|
140
|
+
return super().handle_exception(exc) # type: ignore[misc,no-any-return]
|
|
141
|
+
|
|
142
|
+
def finalize_response(
|
|
143
|
+
self, request: Request, response: Response, *args: Any, **kwargs: Any
|
|
144
|
+
) -> Response:
|
|
145
|
+
response = super().finalize_response(request, response, *args, **kwargs) # type: ignore[misc]
|
|
146
|
+
row = getattr(self, "_idempotency_row", None)
|
|
147
|
+
if row is None:
|
|
148
|
+
return response
|
|
149
|
+
|
|
150
|
+
# DRF's exception_handler calls set_rollback() for handled 4xx/5xx, marking the
|
|
151
|
+
# whole ATOMIC_REQUESTS transaction (including our claim row) for rollback. We must
|
|
152
|
+
# not query a broken transaction, and the claim is discarded with it — so a retry
|
|
153
|
+
# re-runs, which is correct for deterministic errors. Skip persistence entirely.
|
|
154
|
+
if connection.get_rollback():
|
|
155
|
+
return response
|
|
156
|
+
|
|
157
|
+
body = self._storable_body(response)
|
|
158
|
+
if body is _NOT_STORABLE:
|
|
159
|
+
# Non-storable (5xx, streaming, oversized): don't consume the key — delete
|
|
160
|
+
# the claim so a retry re-runs. Same transaction, so this is atomic too.
|
|
161
|
+
row.delete()
|
|
162
|
+
return response
|
|
163
|
+
|
|
164
|
+
row.status = IdempotencyKey.Status.COMPLETED
|
|
165
|
+
row.response_status = response.status_code
|
|
166
|
+
row.response_body = body
|
|
167
|
+
headers = {
|
|
168
|
+
name: response[name]
|
|
169
|
+
for name in SAFELISTED_RESPONSE_HEADERS
|
|
170
|
+
if response.has_header(name)
|
|
171
|
+
}
|
|
172
|
+
row.response_headers = headers or None
|
|
173
|
+
row.save(
|
|
174
|
+
update_fields=[
|
|
175
|
+
"status",
|
|
176
|
+
"response_status",
|
|
177
|
+
"response_body",
|
|
178
|
+
"response_headers",
|
|
179
|
+
]
|
|
180
|
+
)
|
|
181
|
+
return response
|
|
182
|
+
|
|
183
|
+
def _storable_body(self, response: Response) -> Any:
|
|
184
|
+
"""Return JSON-native body to store, or ``_NOT_STORABLE``.
|
|
185
|
+
|
|
186
|
+
Stores the DRF-rendered JSON round-tripped through ``json.loads`` rather than raw
|
|
187
|
+
``response.data`` — the latter may hold dates/Decimals/UUIDs that the default
|
|
188
|
+
JSONField encoder cannot serialize.
|
|
189
|
+
"""
|
|
190
|
+
# Never cache server errors — they propagate/roll back and must stay retryable.
|
|
191
|
+
if response.status_code >= 500:
|
|
192
|
+
return _NOT_STORABLE
|
|
193
|
+
if not hasattr(response, "data"):
|
|
194
|
+
return _NOT_STORABLE
|
|
195
|
+
from rest_framework.renderers import JSONRenderer
|
|
196
|
+
|
|
197
|
+
try:
|
|
198
|
+
rendered = JSONRenderer().render(response.data)
|
|
199
|
+
except (TypeError, ValueError):
|
|
200
|
+
return _NOT_STORABLE
|
|
201
|
+
max_bytes = getattr(settings, "IDEMPOTENCY_MAX_BODY_BYTES", DEFAULT_MAX_BODY_BYTES)
|
|
202
|
+
if len(rendered) > max_bytes:
|
|
203
|
+
return _NOT_STORABLE
|
|
204
|
+
if not rendered:
|
|
205
|
+
return None # e.g. 204 No Content — DRF renders None as empty bytes
|
|
206
|
+
return json.loads(rendered)
|
|
207
|
+
|
|
208
|
+
@staticmethod
|
|
209
|
+
def _compute_request_hash(request: Request) -> str:
|
|
210
|
+
"""sha256 of method + full path (with query) + raw body.
|
|
211
|
+
|
|
212
|
+
Read ``request.body`` here in ``initial()`` — before the handler accesses
|
|
213
|
+
``request.data`` — so Django buffers the raw bytes and the view can still parse
|
|
214
|
+
the body downstream.
|
|
215
|
+
"""
|
|
216
|
+
try:
|
|
217
|
+
body = request.body
|
|
218
|
+
except Exception:
|
|
219
|
+
body = b""
|
|
220
|
+
hasher = hashlib.sha256()
|
|
221
|
+
hasher.update((request.method or "").encode("utf-8"))
|
|
222
|
+
hasher.update(b"\n")
|
|
223
|
+
hasher.update(request.get_full_path().encode("utf-8"))
|
|
224
|
+
hasher.update(b"\n")
|
|
225
|
+
hasher.update(body)
|
|
226
|
+
return hasher.hexdigest()
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Models for client-driven HTTP idempotency (ADR-0170).
|
|
2
|
+
|
|
3
|
+
A single model, :class:`IdempotencyKey`, stores the response to an unsafe mutation
|
|
4
|
+
(POST/PATCH/PUT/DELETE) that carried an ``Idempotency-Key`` header so a retry with the
|
|
5
|
+
same key replays the stored response instead of re-applying the write.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import uuid
|
|
11
|
+
|
|
12
|
+
from django.conf import settings
|
|
13
|
+
from django.db import models
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class IdempotencyKey(models.Model):
|
|
17
|
+
"""Stored response for a client-supplied ``Idempotency-Key`` on an unsafe mutation.
|
|
18
|
+
|
|
19
|
+
Does NOT inherit ``VersionedModel`` — this is a server-side request-dedup record,
|
|
20
|
+
not synced to mobile clients.
|
|
21
|
+
|
|
22
|
+
Invariant (ADR-0170): a *committed* row is always ``status=COMPLETED``. The claim
|
|
23
|
+
row is inserted (``PROCESSING``) and updated to ``COMPLETED`` inside the same
|
|
24
|
+
``ATOMIC_REQUESTS`` transaction as the mutation, so a rolled-back mutation (5xx /
|
|
25
|
+
unhandled exception) leaves no row and a retry re-runs. The ``PROCESSING`` state is
|
|
26
|
+
therefore only ever observed transiently *within* the originating request's open
|
|
27
|
+
transaction; it serves the concurrent-duplicate blocking semantics (a second request
|
|
28
|
+
with the same key blocks on the unique constraint until the first transaction ends).
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
class Status(models.TextChoices):
|
|
32
|
+
PROCESSING = "processing", "Processing"
|
|
33
|
+
COMPLETED = "completed", "Completed"
|
|
34
|
+
|
|
35
|
+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
36
|
+
user = models.ForeignKey(
|
|
37
|
+
settings.AUTH_USER_MODEL,
|
|
38
|
+
on_delete=models.CASCADE,
|
|
39
|
+
related_name="idempotency_keys",
|
|
40
|
+
# The (user, key) unique constraint already indexes user_id as its left prefix,
|
|
41
|
+
# so the implicit per-FK index would be redundant.
|
|
42
|
+
db_index=False,
|
|
43
|
+
)
|
|
44
|
+
# Client-supplied key. A UUID is recommended but any opaque string is accepted;
|
|
45
|
+
# uniqueness is scoped per-user so a leaked key cannot replay another user's response.
|
|
46
|
+
key = models.CharField(max_length=255)
|
|
47
|
+
method = models.CharField(max_length=8)
|
|
48
|
+
path = models.CharField(max_length=512)
|
|
49
|
+
# sha256 hex of method + path-with-query + raw body. A reused key with a different
|
|
50
|
+
# request hash is rejected with 422 (idempotency_key_conflict).
|
|
51
|
+
request_hash = models.CharField(max_length=64)
|
|
52
|
+
status = models.CharField(
|
|
53
|
+
max_length=10,
|
|
54
|
+
choices=Status.choices,
|
|
55
|
+
default=Status.PROCESSING,
|
|
56
|
+
)
|
|
57
|
+
response_status = models.SmallIntegerField(null=True, blank=True)
|
|
58
|
+
response_body = models.JSONField(null=True, blank=True)
|
|
59
|
+
# Safelisted response headers replayed verbatim (currently only Location).
|
|
60
|
+
response_headers = models.JSONField(null=True, blank=True)
|
|
61
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
62
|
+
|
|
63
|
+
class Meta:
|
|
64
|
+
constraints = [
|
|
65
|
+
models.UniqueConstraint(
|
|
66
|
+
fields=["user", "key"],
|
|
67
|
+
name="idempotency_key_unique_per_user",
|
|
68
|
+
),
|
|
69
|
+
]
|
|
70
|
+
indexes = [
|
|
71
|
+
models.Index(fields=["created_at"], name="idempotency_created_idx"),
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
def __str__(self) -> str:
|
|
75
|
+
return f"IdempotencyKey({self.method} {self.path} {self.status})"
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Celery tasks for the idempotency app — retention purge (ADR-0170)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from datetime import timedelta
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from django.conf import settings
|
|
10
|
+
from django.db import OperationalError
|
|
11
|
+
from django.utils import timezone
|
|
12
|
+
|
|
13
|
+
from trueppm_api.core.idempotent import idempotent_task
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _do_purge(retention_hours: int) -> int:
|
|
19
|
+
"""Delete IdempotencyKey rows older than the retention window. Returns deleted count."""
|
|
20
|
+
from trueppm_api.apps.idempotency.models import IdempotencyKey
|
|
21
|
+
|
|
22
|
+
cutoff = timezone.now() - timedelta(hours=retention_hours)
|
|
23
|
+
deleted, _ = IdempotencyKey.objects.filter(created_at__lt=cutoff).delete()
|
|
24
|
+
return deleted
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@idempotent_task(
|
|
28
|
+
lock_key_template="idempotency_purge",
|
|
29
|
+
lock_ttl=300,
|
|
30
|
+
on_contention="skip",
|
|
31
|
+
name="idempotency.purge_old_keys",
|
|
32
|
+
autoretry_for=(ConnectionError, OperationalError),
|
|
33
|
+
retry_backoff=60,
|
|
34
|
+
retry_backoff_max=600,
|
|
35
|
+
retry_jitter=True,
|
|
36
|
+
max_retries=3,
|
|
37
|
+
soft_time_limit=120,
|
|
38
|
+
time_limit=180,
|
|
39
|
+
acks_late=True,
|
|
40
|
+
)
|
|
41
|
+
def purge_old_idempotency_keys(self: object) -> dict[str, Any]:
|
|
42
|
+
"""Delete IdempotencyKey rows older than IDEMPOTENCY_RETENTION_HOURS.
|
|
43
|
+
|
|
44
|
+
Registered in CELERY_BEAT_SCHEDULE to run hourly (a nightly job would let rows live
|
|
45
|
+
up to ~48h, violating the 24h contract). When IDEMPOTENCY_RETENTION_HOURS is None,
|
|
46
|
+
purging is disabled (enterprise unlimited-retention convention, matching the
|
|
47
|
+
*_RETENTION_DAYS = None pattern).
|
|
48
|
+
"""
|
|
49
|
+
retention_hours: int | None = getattr(settings, "IDEMPOTENCY_RETENTION_HOURS", 24)
|
|
50
|
+
if retention_hours is None:
|
|
51
|
+
logger.info("purge_old_idempotency_keys: retention disabled, skipping")
|
|
52
|
+
return {"status": "skipped", "reason": "unlimited retention"}
|
|
53
|
+
|
|
54
|
+
deleted = _do_purge(retention_hours)
|
|
55
|
+
if deleted:
|
|
56
|
+
logger.info(
|
|
57
|
+
"purge_old_idempotency_keys: deleted %d rows older than %d hours",
|
|
58
|
+
deleted,
|
|
59
|
+
retention_hours,
|
|
60
|
+
)
|
|
61
|
+
return {"deleted": deleted}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""External integrations app (ADR-0049).
|
|
2
|
+
|
|
3
|
+
Owns ``IntegrationCredential`` (per-user PAT storage) and the three provider
|
|
4
|
+
registries — ``TASK_LINK_PROVIDERS``, ``OUTGOING_CHANNEL_PROVIDERS``,
|
|
5
|
+
``NOTIFICATION_CHANNELS`` — that Enterprise registers richer connectors
|
|
6
|
+
against at ``AppConfig.ready()``.
|
|
7
|
+
|
|
8
|
+
``TaskLink``, the outgoing webhook ``format`` field, and the email
|
|
9
|
+
notifications preference table all hang off this app's extension points and
|
|
10
|
+
ship in successor issues (#637 / #638 / #639).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
default_app_config = "trueppm_api.apps.integrations.apps.IntegrationsConfig"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""App config for ``apps/integrations`` (ADR-0049)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from django.apps import AppConfig
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class IntegrationsConfig(AppConfig):
|
|
9
|
+
default_auto_field = "django.db.models.BigAutoField"
|
|
10
|
+
name = "trueppm_api.apps.integrations"
|
|
11
|
+
verbose_name = "Integrations"
|
|
12
|
+
|
|
13
|
+
def ready(self) -> None:
|
|
14
|
+
"""Register OSS provider classes against the three ADR-0049 registries.
|
|
15
|
+
|
|
16
|
+
Enterprise's ``AppConfig.ready()`` registers richer providers
|
|
17
|
+
against the same registries at the same hook — no OSS code changes
|
|
18
|
+
required when Enterprise lights up Jira / ServiceNow / Slack App.
|
|
19
|
+
|
|
20
|
+
``NOTIFICATION_CHANNELS`` is populated below with the OSS ``in_app`` /
|
|
21
|
+
``email`` channels, but **nothing consumes the registry yet** — channel
|
|
22
|
+
validation still runs off the ``ProjectNotificationChannel`` TextChoices,
|
|
23
|
+
so a channel registered here is not thereby accepted. Wiring it is #3252.
|
|
24
|
+
Contrast ``OUTGOING_CHANNEL_PROVIDERS``, populated here by #638 with the
|
|
25
|
+
OSS ``generic`` and ``slack`` renderers, which *is* consumed — see
|
|
26
|
+
``apps/webhooks/serializers.py`` and ``apps/webhooks/dispatch.py``.
|
|
27
|
+
"""
|
|
28
|
+
from .external_sources import EXTERNAL_TASK_SOURCES, OSS_EXTERNAL_TASK_SOURCES
|
|
29
|
+
from .notification_channels import OSS_NOTIFICATION_CHANNELS
|
|
30
|
+
from .outgoing import OSS_OUTGOING_CHANNEL_PROVIDERS
|
|
31
|
+
from .providers import OSS_TASK_LINK_PROVIDERS
|
|
32
|
+
from .registry import (
|
|
33
|
+
NOTIFICATION_CHANNELS,
|
|
34
|
+
OUTGOING_CHANNEL_PROVIDERS,
|
|
35
|
+
TASK_LINK_PROVIDERS,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# EXTERNAL_TASK_SOURCES (ADR-0097): user-scoped read-only pull sources.
|
|
39
|
+
# A separate registry from TASK_LINK_PROVIDERS — OSS owns ``jira`` here
|
|
40
|
+
# for personal read-only pull; Enterprise appends governance sources.
|
|
41
|
+
for source in OSS_EXTERNAL_TASK_SOURCES:
|
|
42
|
+
if EXTERNAL_TASK_SOURCES.get(source.key) is None:
|
|
43
|
+
EXTERNAL_TASK_SOURCES.register(source.key, source)
|
|
44
|
+
|
|
45
|
+
for handler in OSS_TASK_LINK_PROVIDERS:
|
|
46
|
+
# Idempotent: skip if a re-import (e.g. tests reloading the app)
|
|
47
|
+
# already registered the handler. ``register()`` raises on the
|
|
48
|
+
# duplicate-key path; the registry-clearing helper used by tests
|
|
49
|
+
# avoids that path by re-registering deliberately.
|
|
50
|
+
if TASK_LINK_PROVIDERS.get(handler.key) is None:
|
|
51
|
+
TASK_LINK_PROVIDERS.register(handler.key, handler)
|
|
52
|
+
|
|
53
|
+
for channel in OSS_OUTGOING_CHANNEL_PROVIDERS:
|
|
54
|
+
if OUTGOING_CHANNEL_PROVIDERS.get(channel.key) is None:
|
|
55
|
+
OUTGOING_CHANNEL_PROVIDERS.register(channel.key, channel)
|
|
56
|
+
|
|
57
|
+
for notif_channel in OSS_NOTIFICATION_CHANNELS:
|
|
58
|
+
if NOTIFICATION_CHANNELS.get(notif_channel.key) is None:
|
|
59
|
+
NOTIFICATION_CHANNELS.register(notif_channel.key, notif_channel)
|