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,148 @@
|
|
|
1
|
+
"""Global rate-limiting kill switch (ADR-0604, extends ADR-0208).
|
|
2
|
+
|
|
3
|
+
An operator-only escape hatch to disable **all** DRF throttling — e.g. to measure
|
|
4
|
+
raw throughput in a k6 load test, or to run the full stack locally without
|
|
5
|
+
fighting per-account limits. Disabling rate limiting removes a DoS/abuse
|
|
6
|
+
protection, so it is deliberately hard to enable by accident:
|
|
7
|
+
|
|
8
|
+
* ``TRUEPPM_RATE_LIMIT_ENABLED`` (bool, default ``True``) must be set false, **and**
|
|
9
|
+
* ``TRUEPPM_RATE_LIMIT_DISABLE_ACK`` must equal :data:`RATE_LIMIT_DISABLE_ACK_SENTINEL`.
|
|
10
|
+
|
|
11
|
+
Both are required in **every** environment (the acknowledgment is *not* gated on
|
|
12
|
+
``DEBUG``): a uniform "disabling a security control always needs the explicit
|
|
13
|
+
sentinel" invariant is simpler to reason about and leaves no ``DEBUG``-dependent
|
|
14
|
+
edge case. A disable request without the exact sentinel is **ignored** — rate
|
|
15
|
+
limiting stays on — and reported at ``CRITICAL``, so a stray env var can never
|
|
16
|
+
silently open a DoS path. The refusal fails toward the *protected* state (limits
|
|
17
|
+
on), never toward an outage: a fat-fingered flag must not remove protection *or*
|
|
18
|
+
crash the app, so this deliberately does **not** ``raise`` the way the
|
|
19
|
+
unencrypted-DB guard does.
|
|
20
|
+
|
|
21
|
+
This is deploy-time operator config, never a UI/API toggle — an authenticated
|
|
22
|
+
user must not be able to switch off a platform DoS protection (ADR-0497). The
|
|
23
|
+
live state is surfaced read-only to workspace admins via ``/health/system/`` and
|
|
24
|
+
a banner, and to monitoring via the ``trueppm.ratelimit.enabled`` OTEL gauge.
|
|
25
|
+
|
|
26
|
+
The logic here is split into small pure functions so both the two-key
|
|
27
|
+
acknowledgment resolution and the DRF-config transform are unit-testable without
|
|
28
|
+
booting Django or mutating process env.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
from typing import TYPE_CHECKING, Any, TypeVar
|
|
34
|
+
|
|
35
|
+
if TYPE_CHECKING:
|
|
36
|
+
from collections.abc import MutableMapping
|
|
37
|
+
|
|
38
|
+
from rest_framework.request import Request
|
|
39
|
+
from rest_framework.throttling import BaseThrottle
|
|
40
|
+
from rest_framework.views import APIView
|
|
41
|
+
|
|
42
|
+
# Bound as a string so ``rest_framework`` is never imported at runtime here — this
|
|
43
|
+
# module is imported at settings-load time, and importing DRF that early risks the
|
|
44
|
+
# ``rest_framework.views`` circular-import hazard documented in core/throttling.py.
|
|
45
|
+
_ThrottleT = TypeVar("_ThrottleT", bound="BaseThrottle")
|
|
46
|
+
|
|
47
|
+
# The exact acknowledgment an operator must set in ``TRUEPPM_RATE_LIMIT_DISABLE_ACK``
|
|
48
|
+
# (alongside ``TRUEPPM_RATE_LIMIT_ENABLED=false``) to actually disable rate
|
|
49
|
+
# limiting. Required in every environment. Documented verbatim in
|
|
50
|
+
# docs/administration and referenced by ADR-0604; treat as a stable operator
|
|
51
|
+
# contract — do not change the string without a deprecation note.
|
|
52
|
+
RATE_LIMIT_DISABLE_ACK_SENTINEL = "i-understand-this-disables-abuse-protection"
|
|
53
|
+
|
|
54
|
+
_DISABLED_MESSAGE = (
|
|
55
|
+
"API rate limiting is DISABLED (TRUEPPM_RATE_LIMIT_ENABLED=false with a valid "
|
|
56
|
+
"acknowledgment). Every DRF throttle — including the fail-closed sync write-path "
|
|
57
|
+
"throttle — is bypassed. This removes DoS/abuse protection and must never be set "
|
|
58
|
+
"on a production-facing deployment."
|
|
59
|
+
)
|
|
60
|
+
_REFUSED_MESSAGE = (
|
|
61
|
+
"Ignoring TRUEPPM_RATE_LIMIT_ENABLED=false: the required acknowledgment "
|
|
62
|
+
"TRUEPPM_RATE_LIMIT_DISABLE_ACK is missing or does not match the documented "
|
|
63
|
+
"sentinel. API rate limiting remains ENABLED. This is the safe default — a stray "
|
|
64
|
+
"disable flag never silently removes abuse protection."
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def resolve_rate_limit_enabled(*, requested_enabled: bool, ack: str) -> tuple[bool, str | None]:
|
|
69
|
+
"""Resolve the effective rate-limit switch from the two operator env inputs.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
requested_enabled: value of ``TRUEPPM_RATE_LIMIT_ENABLED`` (default True).
|
|
73
|
+
ack: value of ``TRUEPPM_RATE_LIMIT_DISABLE_ACK`` (default "").
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
``(enabled, critical_message)``. ``critical_message`` is non-None whenever
|
|
77
|
+
the operator asked to disable rate limiting — either confirming the
|
|
78
|
+
(acknowledged) disable or reporting that an unacknowledged disable was
|
|
79
|
+
refused — so the caller logs it at ``CRITICAL``. The default (enabled)
|
|
80
|
+
path is silent.
|
|
81
|
+
"""
|
|
82
|
+
if requested_enabled:
|
|
83
|
+
return True, None
|
|
84
|
+
if ack == RATE_LIMIT_DISABLE_ACK_SENTINEL:
|
|
85
|
+
return False, _DISABLED_MESSAGE
|
|
86
|
+
# Disable requested without the exact acknowledgment: refuse. Fail toward the
|
|
87
|
+
# protected state (limits ON), never toward an outage.
|
|
88
|
+
return True, _REFUSED_MESSAGE
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def apply_rate_limit_disable(rest_framework_config: MutableMapping[str, Any]) -> None:
|
|
92
|
+
"""Neutralize every DRF throttle in a REST_FRAMEWORK config, in place.
|
|
93
|
+
|
|
94
|
+
Drops ``DEFAULT_THROTTLE_CLASSES`` and sets every ``DEFAULT_THROTTLE_RATES``
|
|
95
|
+
value to ``None`` — DRF's ``SimpleRateThrottle.allow_request`` returns True
|
|
96
|
+
immediately when its resolved rate is ``None``, so this disables *all*
|
|
97
|
+
SimpleRateThrottle-family throttles (defaults, scoped, MCP, invite,
|
|
98
|
+
monte-carlo, share, and any added later) from a single place, with no
|
|
99
|
+
per-class edits. Called from settings only when the operator has disabled
|
|
100
|
+
rate limiting with a valid acknowledgment.
|
|
101
|
+
|
|
102
|
+
The custom Redis ``BaseThrottle`` classes (task-sync, sync-upload, mentions,
|
|
103
|
+
…) do not read these rates, so they check the flag at request time via
|
|
104
|
+
:func:`rate_limiting_disabled` instead. Idempotent.
|
|
105
|
+
"""
|
|
106
|
+
rest_framework_config["DEFAULT_THROTTLE_CLASSES"] = []
|
|
107
|
+
rates = rest_framework_config.get("DEFAULT_THROTTLE_RATES") or {}
|
|
108
|
+
rest_framework_config["DEFAULT_THROTTLE_RATES"] = dict.fromkeys(rates, None)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def bypass_when_disabled(throttle_cls: type[_ThrottleT]) -> type[_ThrottleT]:
|
|
112
|
+
"""Class decorator: allow all requests when the global kill switch is off.
|
|
113
|
+
|
|
114
|
+
Wraps a throttle's ``allow_request`` so it returns ``True`` — checked at
|
|
115
|
+
*request* time, before any Redis work — whenever an operator has disabled
|
|
116
|
+
rate limiting (ADR-0604). Applied to the custom Redis ``BaseThrottle``
|
|
117
|
+
throttles (task-sync, sync-upload, git-webhook, task-link-refresh, mentions,
|
|
118
|
+
token-issuance, attachment-upload), which resolve their own limits from Redis
|
|
119
|
+
and so are *not* covered by the ``DEFAULT_THROTTLE_RATES`` transform in
|
|
120
|
+
settings. One decorator line per class keeps the bypass explicit, greppable,
|
|
121
|
+
and testable via ``override_settings`` — with no edit inside each method body.
|
|
122
|
+
|
|
123
|
+
Deliberately bypasses even the fail-closed sync write-path throttle: an
|
|
124
|
+
operator who acknowledged the disable gets "off means off" with no hidden
|
|
125
|
+
exceptions, so the read-only status surfaced to admins is literally accurate.
|
|
126
|
+
"""
|
|
127
|
+
original = throttle_cls.allow_request
|
|
128
|
+
|
|
129
|
+
def allow_request(self: BaseThrottle, request: Request, view: APIView) -> bool:
|
|
130
|
+
if rate_limiting_disabled():
|
|
131
|
+
return True
|
|
132
|
+
return bool(original(self, request, view))
|
|
133
|
+
|
|
134
|
+
throttle_cls.allow_request = allow_request # type: ignore[method-assign]
|
|
135
|
+
return throttle_cls
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def rate_limiting_disabled() -> bool:
|
|
139
|
+
"""True when the global kill switch has disabled all throttling.
|
|
140
|
+
|
|
141
|
+
Read at *request* time by every custom Redis throttle so ``override_settings``
|
|
142
|
+
and a live operator change take effect. Defaults to False (limits on) if the
|
|
143
|
+
setting is somehow absent, so a misconfiguration never silently drops
|
|
144
|
+
protection.
|
|
145
|
+
"""
|
|
146
|
+
from django.conf import settings
|
|
147
|
+
|
|
148
|
+
return getattr(settings, "RATE_LIMIT_ENABLED", True) is False
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Atomic INCR-with-TTL primitives for the raw-redis throttle counters (#1757).
|
|
2
|
+
|
|
3
|
+
Every raw-redis throttle under ``apps/*/throttles.py`` bounds a request rate by
|
|
4
|
+
INCRementing a per-bucket counter key and setting a TTL so the window resets.
|
|
5
|
+
The historical idiom did this as two separate round trips::
|
|
6
|
+
|
|
7
|
+
count = client.incr(key)
|
|
8
|
+
if count == 1:
|
|
9
|
+
client.expire(key, ttl)
|
|
10
|
+
|
|
11
|
+
A crash — or a dropped connection, or an EXPIRE error — *between* the INCR and
|
|
12
|
+
the EXPIRE leaves the counter key with **no TTL**: it never resets and that
|
|
13
|
+
principal is wedged at HTTP 429 for that bucket indefinitely, a self-inflicted
|
|
14
|
+
DoS (flagged Low/pre-existing in the #1719 security review). Collapsing both
|
|
15
|
+
commands into one server-side Lua script makes the TTL inseparable from the
|
|
16
|
+
counter's creation — Redis runs the script atomically, so a bucket either exists
|
|
17
|
+
*with* a TTL or does not exist at all. There is no interleaving in which the
|
|
18
|
+
counter outlives its window.
|
|
19
|
+
|
|
20
|
+
``projects.throttles.claim_visit_window`` and
|
|
21
|
+
``integrations.throttles.claim_webhook_delivery`` already have this guarantee via
|
|
22
|
+
the atomic ``SET NX EX`` idiom; these helpers give the *counter* throttles the
|
|
23
|
+
equivalent. The script text is passed to ``EVAL`` on every call: it is ~40 bytes,
|
|
24
|
+
so the bandwidth cost is negligible and we avoid any ``NOSCRIPT`` cache-miss
|
|
25
|
+
handling that ``EVALSHA`` would require.
|
|
26
|
+
|
|
27
|
+
Both helpers raise ``redis.RedisError`` straight through on a broker failure —
|
|
28
|
+
each caller keeps its own fail-open (rate throttles) or fail-closed (the
|
|
29
|
+
write-path sync throttle) policy exactly as before.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
from typing import TYPE_CHECKING
|
|
35
|
+
|
|
36
|
+
if TYPE_CHECKING:
|
|
37
|
+
import redis
|
|
38
|
+
|
|
39
|
+
# INCR the key and, only on the transition to 1 (the first hit of a fresh
|
|
40
|
+
# window), stamp the TTL. Running both inside one EVAL means the EXPIRE can never
|
|
41
|
+
# be lost between two round trips. Returns the post-increment count.
|
|
42
|
+
_INCR_WITH_TTL_LUA = """
|
|
43
|
+
local count = redis.call('INCR', KEYS[1])
|
|
44
|
+
if count == 1 then
|
|
45
|
+
redis.call('EXPIRE', KEYS[1], ARGV[1])
|
|
46
|
+
end
|
|
47
|
+
return count
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
# INCRBY the key by ARGV[1] and (re)stamp the TTL unconditionally. Used where the
|
|
51
|
+
# counter is advanced by a variable amount and the window is meant to slide from
|
|
52
|
+
# the most recent activity (mention fan-out accounting; the sync in-flight
|
|
53
|
+
# semaphore, whose TTL is a crash-leak guard that should refresh on every entry).
|
|
54
|
+
_INCRBY_WITH_TTL_LUA = """
|
|
55
|
+
local count = redis.call('INCRBY', KEYS[1], ARGV[1])
|
|
56
|
+
redis.call('EXPIRE', KEYS[1], ARGV[2])
|
|
57
|
+
return count
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def incr_with_ttl(client: redis.Redis, key: str, ttl: int) -> int:
|
|
62
|
+
"""Atomically INCR ``key`` and, on the first hit, set its ``ttl`` (seconds).
|
|
63
|
+
|
|
64
|
+
Drop-in replacement for the non-atomic ``INCR`` + ``if count == 1: EXPIRE``
|
|
65
|
+
idiom so a crash between the two can no longer strand a counter without a TTL.
|
|
66
|
+
Returns the post-increment count. Propagates ``redis.RedisError`` so the
|
|
67
|
+
caller keeps its existing fail-open / fail-closed policy.
|
|
68
|
+
"""
|
|
69
|
+
# redis-py types eval() as Awaitable|str for the sync/async-generic client;
|
|
70
|
+
# the sync client returns the script's integer result. Matches the existing
|
|
71
|
+
# ``int(client.incr(...)) # type: ignore[arg-type]`` pattern in the throttles.
|
|
72
|
+
return int(client.eval(_INCR_WITH_TTL_LUA, 1, key, ttl)) # type: ignore[arg-type]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def incrby_with_ttl(client: redis.Redis, key: str, amount: int, ttl: int) -> int:
|
|
76
|
+
"""Atomically INCRBY ``key`` by ``amount`` and (re)set its ``ttl`` (seconds).
|
|
77
|
+
|
|
78
|
+
Like :func:`incr_with_ttl` but for the variable-amount counters, and it stamps
|
|
79
|
+
the TTL on *every* call (a sliding window) rather than only the first. Returns
|
|
80
|
+
the post-increment count. Propagates ``redis.RedisError``.
|
|
81
|
+
"""
|
|
82
|
+
return int(client.eval(_INCRBY_WITH_TTL_LUA, 1, key, amount, ttl)) # type: ignore[arg-type]
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""Refresh-cookie ``SameSite`` validation (#3556, follows ADR-0604's ack pattern).
|
|
2
|
+
|
|
3
|
+
``TRUEPPM_AUTH_REFRESH_COOKIE_SAMESITE`` was, before this module, a free-form env
|
|
4
|
+
override with no validation: whatever string an operator set landed verbatim on
|
|
5
|
+
``Set-Cookie`` (see ``_set_refresh_cookie`` in ``core/auth_views.py``). The refresh
|
|
6
|
+
and logout endpoints are authenticated by that cookie alone — no CSRF token, no
|
|
7
|
+
double-submit — so ``SameSite=Strict`` (the shipped default) is the *only* control
|
|
8
|
+
that stops a forged cross-site request from riding the cookie. An operator who
|
|
9
|
+
sets the override to ``None`` (or, for a split-origin deploy, ``Lax``) removes or
|
|
10
|
+
weakens that control, and nothing said so.
|
|
11
|
+
|
|
12
|
+
This mirrors ``core/ratelimit.py``'s two-key acknowledgment shape rather than
|
|
13
|
+
inventing a new one: disabling a security control always needs an explicit,
|
|
14
|
+
documented sentinel, and the resolution is a pure function so it is unit-testable
|
|
15
|
+
without booting Django or mutating process env. Unlike the rate-limit switch this
|
|
16
|
+
one is a three-way choice, not a boolean, so the return shape carries a
|
|
17
|
+
``warning`` and a ``critical`` slot instead of one message — at most one of the
|
|
18
|
+
two is populated for any resolution:
|
|
19
|
+
|
|
20
|
+
* ``Strict`` (default or explicit) — the safe value. Silent.
|
|
21
|
+
* ``Lax`` — accepted as-is (a split-origin/iframe deploy may need it), but logged
|
|
22
|
+
at WARNING naming the consequence: the cookie stops riding cross-site *POSTs*
|
|
23
|
+
from another origin (the actual CSRF vector these endpoints care about), while
|
|
24
|
+
still riding a top-level cross-site *navigation*.
|
|
25
|
+
* ``None`` with the exact acknowledgment sentinel
|
|
26
|
+
(:data:`REFRESH_COOKIE_SAMESITE_NONE_ACK_SENTINEL` in
|
|
27
|
+
``TRUEPPM_AUTH_REFRESH_COOKIE_SAMESITE_NONE_ACK``) — accepted, but logged at
|
|
28
|
+
CRITICAL: this removes the SameSite CSRF backstop entirely and relies solely on
|
|
29
|
+
the ``Sec-Fetch-Site`` / ``Origin`` defense-in-depth check in
|
|
30
|
+
``core/auth_views.py``.
|
|
31
|
+
* ``None`` without the sentinel, or any value that is not one of the three
|
|
32
|
+
recognized words — refused. Falls back to ``Strict`` (fail toward the
|
|
33
|
+
*protected* state, exactly like an unacknowledged rate-limit disable) and logs
|
|
34
|
+
at CRITICAL so a typo or a fat-fingered override can never silently weaken the
|
|
35
|
+
cookie; it can only be silently ignored in favor of the safe default.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
from __future__ import annotations
|
|
39
|
+
|
|
40
|
+
from typing import Literal, NamedTuple
|
|
41
|
+
|
|
42
|
+
#: The precise literal type Django's ``HttpResponseBase.set_cookie``/``delete_cookie``
|
|
43
|
+
#: stubs declare for ``samesite=``. Typing :attr:`RefreshCookieSameSiteResolution.value`
|
|
44
|
+
#: as this — rather than a bare ``str`` — is what lets ``AUTH_REFRESH_COOKIE_SAMESITE``
|
|
45
|
+
#: type-check at its call sites in ``core/auth_views.py`` (django-stubs' mypy plugin
|
|
46
|
+
#: infers a settings attribute's type from its assignment in ``settings/base.py``).
|
|
47
|
+
SameSiteValue = Literal["Strict", "Lax", "None"]
|
|
48
|
+
|
|
49
|
+
#: The exact acknowledgment an operator must set in
|
|
50
|
+
#: ``TRUEPPM_AUTH_REFRESH_COOKIE_SAMESITE_NONE_ACK`` (alongside
|
|
51
|
+
#: ``TRUEPPM_AUTH_REFRESH_COOKIE_SAMESITE=None``) to actually drop SameSite
|
|
52
|
+
#: protection on the refresh cookie. Documented verbatim in
|
|
53
|
+
#: administration/configuration.md; treat as a stable operator contract — do not
|
|
54
|
+
#: change the string without a deprecation note.
|
|
55
|
+
REFRESH_COOKIE_SAMESITE_NONE_ACK_SENTINEL = "i-understand-this-removes-refresh-csrf-protection"
|
|
56
|
+
|
|
57
|
+
#: The only values Django's cookie machinery treats as a real SameSite policy.
|
|
58
|
+
#: Compared case-insensitively so ``lax``/``LAX``/``Lax`` all resolve the same way,
|
|
59
|
+
#: but the *stored* setting is always one of these three canonical spellings —
|
|
60
|
+
#: never the operator's raw casing — so nothing downstream (the ``Set-Cookie``
|
|
61
|
+
#: header, ``_clear_refresh_cookie``'s matching ``delete_cookie`` call) can drift
|
|
62
|
+
#: from what this function decided.
|
|
63
|
+
_VALID_SAMESITE_VALUES = ("Strict", "Lax", "None")
|
|
64
|
+
|
|
65
|
+
_LAX_WARNING = (
|
|
66
|
+
"TRUEPPM_AUTH_REFRESH_COOKIE_SAMESITE=Lax: the refresh cookie no longer blocks "
|
|
67
|
+
"cross-site requests unconditionally. Lax still withholds the cookie from a "
|
|
68
|
+
"cross-site POST (the vector that matters for CookieTokenRefreshView / "
|
|
69
|
+
"CookieTokenLogoutView), but attaches it to a cross-site top-level GET "
|
|
70
|
+
"navigation. Use this only for a deploy that genuinely needs the cookie to "
|
|
71
|
+
"survive top-level navigation from another site; the Sec-Fetch-Site / Origin "
|
|
72
|
+
"check in core/auth_views.py is defense in depth, not a substitute."
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
_NONE_ACKED_CRITICAL = (
|
|
76
|
+
"TRUEPPM_AUTH_REFRESH_COOKIE_SAMESITE=None is ACTIVE (acknowledged via "
|
|
77
|
+
"TRUEPPM_AUTH_REFRESH_COOKIE_SAMESITE_NONE_ACK). The refresh cookie now rides "
|
|
78
|
+
"on every cross-site request, browser-enforced SameSite protection is fully "
|
|
79
|
+
"removed from CookieTokenRefreshView and CookieTokenLogoutView, and the only "
|
|
80
|
+
"remaining control is the Sec-Fetch-Site / Origin check in core/auth_views.py "
|
|
81
|
+
"— which a non-browser client bypasses by sending neither header. Set this "
|
|
82
|
+
"only when TruePPM is genuinely framed or called cross-site and you have "
|
|
83
|
+
"verified CSRF_TRUSTED_ORIGINS is complete."
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
_NONE_UNACKED_CRITICAL = (
|
|
87
|
+
"Ignoring TRUEPPM_AUTH_REFRESH_COOKIE_SAMESITE=None: it removes the only "
|
|
88
|
+
"unconditional CSRF control on the refresh/logout endpoints, and the required "
|
|
89
|
+
"acknowledgment TRUEPPM_AUTH_REFRESH_COOKIE_SAMESITE_NONE_ACK is missing or "
|
|
90
|
+
"does not match the documented sentinel. Falling back to SameSite=Strict. "
|
|
91
|
+
"This is the safe default — a stray override never silently removes the "
|
|
92
|
+
"cookie's CSRF protection."
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
_UNRECOGNIZED_CRITICAL_TEMPLATE = (
|
|
96
|
+
"TRUEPPM_AUTH_REFRESH_COOKIE_SAMESITE={value!r} is not a recognized SameSite "
|
|
97
|
+
"value (Strict, Lax, None) — falling back to SameSite=Strict. An unrecognized "
|
|
98
|
+
"value is not a Django CSRF posture at all; browsers that honor the raw string "
|
|
99
|
+
"verbatim may fail closed and drop the cookie, while a permissive parser could "
|
|
100
|
+
"treat it as Lax or None. Set the value to one of Strict / Lax / None, or unset "
|
|
101
|
+
"the variable to accept the Strict default."
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class RefreshCookieSameSiteResolution(NamedTuple):
|
|
106
|
+
"""The effective ``SameSite`` value plus at most one log-worthy message."""
|
|
107
|
+
|
|
108
|
+
value: SameSiteValue
|
|
109
|
+
warning: str | None
|
|
110
|
+
critical: str | None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def resolve_refresh_cookie_samesite(*, requested: str, ack: str) -> RefreshCookieSameSiteResolution:
|
|
114
|
+
"""Resolve the effective refresh-cookie ``SameSite`` value from operator env.
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
requested: raw ``TRUEPPM_AUTH_REFRESH_COOKIE_SAMESITE`` (or legacy
|
|
118
|
+
``AUTH_REFRESH_COOKIE_SAMESITE``) value, already applying that
|
|
119
|
+
fallback chain — this function only validates the resolved string.
|
|
120
|
+
ack: raw ``TRUEPPM_AUTH_REFRESH_COOKIE_SAMESITE_NONE_ACK`` value.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
A :class:`RefreshCookieSameSiteResolution`. ``value`` is always one of
|
|
124
|
+
``"Strict"``, ``"Lax"``, ``"None"`` — never the raw operator string — so
|
|
125
|
+
callers can pass it straight to ``response.set_cookie(samesite=...)``
|
|
126
|
+
without re-validating.
|
|
127
|
+
"""
|
|
128
|
+
stripped = (requested or "").strip()
|
|
129
|
+
lowered = stripped.lower()
|
|
130
|
+
normalized = next(
|
|
131
|
+
(candidate for candidate in _VALID_SAMESITE_VALUES if candidate.lower() == lowered),
|
|
132
|
+
None,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
if normalized is None:
|
|
136
|
+
# Blank resolves through the env() default chain to "Strict" before this
|
|
137
|
+
# function ever runs, so an empty string here means the operator set the
|
|
138
|
+
# var to whitespace/empty explicitly — treat it the same as any other
|
|
139
|
+
# unrecognized value rather than special-casing it into silence.
|
|
140
|
+
message = _UNRECOGNIZED_CRITICAL_TEMPLATE.format(value=requested)
|
|
141
|
+
return RefreshCookieSameSiteResolution(value="Strict", warning=None, critical=message)
|
|
142
|
+
|
|
143
|
+
if normalized == "Strict":
|
|
144
|
+
return RefreshCookieSameSiteResolution(value="Strict", warning=None, critical=None)
|
|
145
|
+
|
|
146
|
+
if normalized == "Lax":
|
|
147
|
+
return RefreshCookieSameSiteResolution(value="Lax", warning=_LAX_WARNING, critical=None)
|
|
148
|
+
|
|
149
|
+
# normalized == "None"
|
|
150
|
+
if ack == REFRESH_COOKIE_SAMESITE_NONE_ACK_SENTINEL:
|
|
151
|
+
return RefreshCookieSameSiteResolution(
|
|
152
|
+
value="None", warning=None, critical=_NONE_ACKED_CRITICAL
|
|
153
|
+
)
|
|
154
|
+
return RefreshCookieSameSiteResolution(
|
|
155
|
+
value="Strict", warning=None, critical=_NONE_UNACKED_CRITICAL
|
|
156
|
+
)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Narrowing helpers for a JSON request body that has to be an object.
|
|
2
|
+
|
|
3
|
+
DRF types ``Request.data`` as ``dict[str, Any] | list[Any]`` because a JSON body
|
|
4
|
+
may legitimately be a top-level array. An endpoint that reads *named* fields off
|
|
5
|
+
it therefore has to establish that the body is an object **before** the first
|
|
6
|
+
``.get(...)`` or ``[...]`` — a ``list`` has neither, so reaching for one raises
|
|
7
|
+
``AttributeError``/``TypeError`` and DRF returns a **500**, not the 400 the
|
|
8
|
+
caller is owed.
|
|
9
|
+
|
|
10
|
+
That is the #2795 class, and its defining trait is that the value-type guards
|
|
11
|
+
these endpoints already carry (``if not isinstance(presenter, str)``) sit
|
|
12
|
+
*downstream* of the container access that already blew up, so they never run.
|
|
13
|
+
The container check has to come first, and it has to come before any write.
|
|
14
|
+
|
|
15
|
+
Use :func:`object_body` at the top of any handler that reads named fields. Where
|
|
16
|
+
a non-object body is genuinely unreachable — a view whose ``parser_classes`` are
|
|
17
|
+
multipart-only, or one whose envelope serializer has already rejected a
|
|
18
|
+
non-mapping — narrow with a ``cast`` and say *why* in a comment instead; a guard
|
|
19
|
+
that can never fire is untestable and reads as protection that is not there.
|
|
20
|
+
|
|
21
|
+
Note the neighbour with the opposite rule: ``_task_body_mapping()`` in
|
|
22
|
+
``apps/projects/views.py`` coerces a non-mapping to ``{}`` rather than refusing
|
|
23
|
+
it. That is safe only because a ``ModelSerializer`` has already rejected non-dicts
|
|
24
|
+
at both its call sites — do not copy the pattern to a handler that reads
|
|
25
|
+
``request.data`` first.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
from typing import Any
|
|
31
|
+
|
|
32
|
+
from rest_framework import status
|
|
33
|
+
from rest_framework.exceptions import APIException
|
|
34
|
+
from rest_framework.request import Request
|
|
35
|
+
|
|
36
|
+
#: The one body every non-object refusal puts on the wire (#3281).
|
|
37
|
+
#:
|
|
38
|
+
#: A **dict** ``default_detail``, not a string, and that is the whole mechanism:
|
|
39
|
+
#: DRF's handler passes a ``dict``/``list`` detail through verbatim and only wraps
|
|
40
|
+
#: a *string* one as ``{"detail": ...}``. A string here therefore drops
|
|
41
|
+
#: ``default_code`` on the floor — it stays on the ``ErrorDetail`` object and the
|
|
42
|
+
#: client never sees it, which is exactly what #3278 shipped and what left one
|
|
43
|
+
#: refusal speaking three different bodies.
|
|
44
|
+
INVALID_BODY_DETAIL = "Request body must be a JSON object."
|
|
45
|
+
INVALID_BODY_CODE = "invalid_body"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class InvalidRequestBody(APIException):
|
|
49
|
+
"""400 for a JSON body that parsed fine but is not an object.
|
|
50
|
+
|
|
51
|
+
A dedicated exception rather than ``ValidationError``: the latter would wrap
|
|
52
|
+
the message in a list under a field key and describe a *field* problem, and
|
|
53
|
+
this is a problem with the envelope — no field is at fault.
|
|
54
|
+
|
|
55
|
+
Shape 2 (``docs/api/errors.md``) — ``{"code", "detail"}`` — because the page
|
|
56
|
+
reserves the ``code`` for failures a client is expected to *handle*
|
|
57
|
+
differently rather than merely report, and a body the caller constructed
|
|
58
|
+
wrongly is a bug in its request construction, not a rejected field value.
|
|
59
|
+
|
|
60
|
+
Converging **upward** is what makes this non-breaking: ``detail`` is still
|
|
61
|
+
present everywhere it already was, so no client that reads it breaks, and the
|
|
62
|
+
group/ungroup guard that already emitted ``code`` is byte-identical. Stripping
|
|
63
|
+
``code`` to match the other two would have been the client-visible direction.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
status_code = status.HTTP_400_BAD_REQUEST
|
|
67
|
+
default_detail = {"code": INVALID_BODY_CODE, "detail": INVALID_BODY_DETAIL} # noqa: RUF012
|
|
68
|
+
default_code = INVALID_BODY_CODE
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def object_body(request: Request) -> dict[str, Any]:
|
|
72
|
+
"""Return the request body as a mapping, or raise :class:`InvalidRequestBody`.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
request: The DRF request whose parsed body is being read.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
The parsed body, narrowed to ``dict[str, Any]``.
|
|
79
|
+
|
|
80
|
+
Raises:
|
|
81
|
+
InvalidRequestBody: The body parsed to a list or other non-mapping.
|
|
82
|
+
"""
|
|
83
|
+
data = request.data
|
|
84
|
+
if not isinstance(data, dict):
|
|
85
|
+
raise InvalidRequestBody
|
|
86
|
+
return data
|