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
|
File without changes
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""App config for sync."""
|
|
2
|
+
|
|
3
|
+
from django.apps import AppConfig
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SyncConfig(AppConfig):
|
|
7
|
+
default_auto_field = "django.db.models.BigAutoField"
|
|
8
|
+
name = "trueppm_api.apps.sync"
|
|
9
|
+
|
|
10
|
+
def ready(self) -> None:
|
|
11
|
+
# Wire the per-project sync sequence that allocates `sync_seq`
|
|
12
|
+
# (ADR-0686, #2491).
|
|
13
|
+
from trueppm_api.apps.sync.sequence import register_sequence_receivers
|
|
14
|
+
|
|
15
|
+
register_sequence_receivers()
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""Data-backfill helper for the sync delta cursor (ADR-0686, #2491).
|
|
2
|
+
|
|
3
|
+
Extracted from the migration file so tests can import it without coupling to a
|
|
4
|
+
migration file *name*, which a squash deletes (CLAUDE.md migration rule 3).
|
|
5
|
+
|
|
6
|
+
Seeds every existing synced row's ``sync_seq`` from its project's sequence. Every
|
|
7
|
+
row in a project gets the **same** value — one step of the sequence for the whole
|
|
8
|
+
pre-existing dataset. That is correct, not a shortcut: the delta is ``> since``
|
|
9
|
+
and the checkpoint is the maximum, so rows sharing a value are either all before
|
|
10
|
+
or all after any client checkpoint. It is the same property that lets a batched
|
|
11
|
+
upload share one value (ADR-0686).
|
|
12
|
+
|
|
13
|
+
Because the value is the project's old watermark **plus one**, every ``since`` an
|
|
14
|
+
existing client holds is strictly below it. The first pull after upgrade
|
|
15
|
+
redelivers the project once under WatermelonDB upsert semantics — which also
|
|
16
|
+
repairs whatever the old scheme had already dropped — and every pull after that
|
|
17
|
+
is a normal delta. No client change, and no forced re-pull from zero.
|
|
18
|
+
|
|
19
|
+
Soft-deleted rows are renumbered too: they are tombstones the delta must still
|
|
20
|
+
deliver, selected by the same ``sync_seq__gt=since`` filter and split on
|
|
21
|
+
``is_deleted``.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from typing import Any
|
|
27
|
+
|
|
28
|
+
# The two join shapes every indirect source uses to reach its project row, and
|
|
29
|
+
# the predicates that bind the target table to the end of that join. Named
|
|
30
|
+
# because the table below is thirteen rows of near-identical SQL otherwise: the
|
|
31
|
+
# names say *how* a model reaches its project (directly, or through its task),
|
|
32
|
+
# which is the only thing that varies between most rows.
|
|
33
|
+
_VIA_PROJECT = "{project} p"
|
|
34
|
+
_VIA_TASK = "{task} t JOIN {project} p ON t.project_id = p.id"
|
|
35
|
+
_ON_PROJECT_ID = "x.project_id = p.id"
|
|
36
|
+
_ON_TASK_ID = "x.task_id = t.id"
|
|
37
|
+
|
|
38
|
+
# (label, join clause reaching the project row, predicate binding the target).
|
|
39
|
+
# The target table is aliased ``x`` and the project row ``p`` in every statement.
|
|
40
|
+
_INDIRECT_SOURCES: list[tuple[str, str, str]] = [
|
|
41
|
+
("projects.Task", _VIA_PROJECT, _ON_PROJECT_ID),
|
|
42
|
+
("access.ProjectMembership", _VIA_PROJECT, _ON_PROJECT_ID),
|
|
43
|
+
("projects.Risk", _VIA_PROJECT, _ON_PROJECT_ID),
|
|
44
|
+
("projects.Sprint", _VIA_PROJECT, _ON_PROJECT_ID),
|
|
45
|
+
("projects.Label", _VIA_PROJECT, _ON_PROJECT_ID),
|
|
46
|
+
(
|
|
47
|
+
"projects.SprintRetro",
|
|
48
|
+
"{sprint} s JOIN {project} p ON s.project_id = p.id",
|
|
49
|
+
"x.sprint_id = s.id",
|
|
50
|
+
),
|
|
51
|
+
(
|
|
52
|
+
"projects.RetroActionItem",
|
|
53
|
+
"{sprintretro} r JOIN {sprint} s ON r.sprint_id = s.id "
|
|
54
|
+
"JOIN {project} p ON s.project_id = p.id",
|
|
55
|
+
"x.retro_id = r.id",
|
|
56
|
+
),
|
|
57
|
+
("projects.TaskSuggestedAssignee", _VIA_TASK, _ON_TASK_ID),
|
|
58
|
+
("projects.TaskRecurrenceRule", _VIA_TASK, _ON_TASK_ID),
|
|
59
|
+
("integrations.TaskLink", _VIA_TASK, _ON_TASK_ID),
|
|
60
|
+
("timetracking.TimeEntry", _VIA_TASK, _ON_TASK_ID),
|
|
61
|
+
# Graph edges reach the project through the endpoint the delta scopes and
|
|
62
|
+
# orders by. Both are delivered as their own collection filtered on their own
|
|
63
|
+
# `sync_seq`, so an unseeded edge would stay at 0 and never be delivered.
|
|
64
|
+
("projects.Dependency", _VIA_TASK, "x.predecessor_id = t.id"),
|
|
65
|
+
("projects.TaskRelation", _VIA_TASK, "x.source_id = t.id"),
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def seed_sync_seq(apps: Any, schema_editor: Any) -> None:
|
|
70
|
+
"""Give every existing synced row a cursor drawn from its project's sequence.
|
|
71
|
+
|
|
72
|
+
Every collection the delta serves is seeded, graph edges included: each is
|
|
73
|
+
filtered on its *own* ``sync_seq``, so a row left at 0 would never be
|
|
74
|
+
delivered again — not even by a cold-start ``since=0`` pull.
|
|
75
|
+
"""
|
|
76
|
+
get = apps.get_model
|
|
77
|
+
tables = {
|
|
78
|
+
"project": get("projects", "Project")._meta.db_table,
|
|
79
|
+
"task": get("projects", "Task")._meta.db_table,
|
|
80
|
+
"sprint": get("projects", "Sprint")._meta.db_table,
|
|
81
|
+
"sprintretro": get("projects", "SprintRetro")._meta.db_table,
|
|
82
|
+
"calendar": get("projects", "Calendar")._meta.db_table,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
with schema_editor.connection.cursor() as cursor:
|
|
86
|
+
# The project row is itself a synced source. Do it first, while
|
|
87
|
+
# last_sync_version still holds the pre-migration watermark.
|
|
88
|
+
#
|
|
89
|
+
# Every identifier interpolated into the statements below is a db_table
|
|
90
|
+
# read from model _meta; no user input reaches them, and there is no
|
|
91
|
+
# value to bind as a parameter.
|
|
92
|
+
# nosemgrep: formatted-sql-query, sqlalchemy-execute-raw-query
|
|
93
|
+
cursor.execute(
|
|
94
|
+
f"UPDATE {tables['project']} SET sync_seq = last_sync_version + 1" # nosec B608
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
for label, join, predicate in _INDIRECT_SOURCES:
|
|
98
|
+
app_label, model_name = label.split(".")
|
|
99
|
+
table = get(app_label, model_name)._meta.db_table
|
|
100
|
+
cursor.execute(
|
|
101
|
+
f"UPDATE {table} x SET sync_seq = p.last_sync_version + 1 " # nosec B608
|
|
102
|
+
f"FROM {join.format(**tables)} WHERE {predicate}"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# A calendar can be shared, and one row carries one cursor, so it takes
|
|
106
|
+
# the maximum over its owners. The final statement then raises every
|
|
107
|
+
# owning project's watermark to at least that value — without it a shared
|
|
108
|
+
# calendar would sit permanently above one owner's checkpoint and be
|
|
109
|
+
# redelivered on every pull.
|
|
110
|
+
cursor.execute(
|
|
111
|
+
f"UPDATE {tables['calendar']} c SET sync_seq = sub.v FROM (" # nosec B608
|
|
112
|
+
f" SELECT calendar_id, MAX(last_sync_version) + 1 AS v"
|
|
113
|
+
f" FROM {tables['project']} WHERE calendar_id IS NOT NULL"
|
|
114
|
+
f" GROUP BY calendar_id"
|
|
115
|
+
f") sub WHERE c.id = sub.calendar_id"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
cursor.execute(
|
|
119
|
+
f"UPDATE {tables['project']} p SET last_sync_version = GREATEST(" # nosec B608
|
|
120
|
+
f" p.last_sync_version + 1,"
|
|
121
|
+
f" COALESCE((SELECT c.sync_seq FROM {tables['calendar']} c"
|
|
122
|
+
f" WHERE c.id = p.calendar_id), 0))"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def unseed_sync_seq(apps: Any, schema_editor: Any) -> None:
|
|
127
|
+
"""Reverse: leave the column at its default.
|
|
128
|
+
|
|
129
|
+
``last_sync_version`` is deliberately *not* wound back. It is monotonic by
|
|
130
|
+
contract, and a watermark that is too high only costs a client a pull that
|
|
131
|
+
returns nothing; one that moves backwards would redeliver rows forever.
|
|
132
|
+
"""
|
|
133
|
+
get = apps.get_model
|
|
134
|
+
labels = ["projects.Project", "projects.Calendar", *[s[0] for s in _INDIRECT_SOURCES]]
|
|
135
|
+
with schema_editor.connection.cursor() as cursor:
|
|
136
|
+
for label in labels:
|
|
137
|
+
app_label, model_name = label.split(".")
|
|
138
|
+
table = get(app_label, model_name)._meta.db_table
|
|
139
|
+
# nosemgrep: formatted-sql-query, sqlalchemy-execute-raw-query
|
|
140
|
+
cursor.execute(f"UPDATE {table} SET sync_seq = 0") # nosec B608
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def seed_program_sync_seq(apps: Any, schema_editor: Any) -> None:
|
|
144
|
+
"""Seed the installation-wide program cursor for existing data (ADR-0747, #2498).
|
|
145
|
+
|
|
146
|
+
``Program`` and ``ProgramMembership`` have carried a ``sync_seq`` column since
|
|
147
|
+
#2491, but always ``0`` — neither model allocated, because the program slice
|
|
148
|
+
keyed on ``server_version``. This assigns real, ordered values and starts the
|
|
149
|
+
installation counter above them.
|
|
150
|
+
|
|
151
|
+
Unlike :func:`seed_sync_seq`, rows are numbered **individually**, in
|
|
152
|
+
``(created_at, id)`` order, rather than all sharing one value. The project
|
|
153
|
+
backfill can share a value because a project's rows are re-delivered together
|
|
154
|
+
anyway; here the whole point is that a single scalar cursor must order rows
|
|
155
|
+
from independent programs against each other, so the backfill has to produce
|
|
156
|
+
the total order the runtime allocator will continue.
|
|
157
|
+
|
|
158
|
+
Each table is ordered by its own creation timestamp — ``Program.created_at``,
|
|
159
|
+
``ProgramMembership.joined_at``; the two models do not share a field name — with
|
|
160
|
+
``id`` breaking ties, since two rows can share a timestamp. That makes the
|
|
161
|
+
numbering deterministic and reproducible rather than dependent on physical row
|
|
162
|
+
order.
|
|
163
|
+
|
|
164
|
+
**The offset is deliberate.** Numbering starts at
|
|
165
|
+
``max(server_version across both tables) + 1`` rather than at 1, so that every
|
|
166
|
+
``since`` an existing client holds — drawn from the *old* ``server_version``
|
|
167
|
+
scale — sorts strictly below every row's new ``sync_seq``. The two scales are
|
|
168
|
+
otherwise unrelated, and a client whose stored cursor happened to exceed the
|
|
169
|
+
new values would silently skip rows on its first post-upgrade pull. Starting
|
|
170
|
+
above every plausible old value makes that first pull a full re-delivery of the
|
|
171
|
+
caller's accessible set, which is safe under WatermelonDB upsert and is the
|
|
172
|
+
only transition that cannot drop a row. It also repairs whatever the old
|
|
173
|
+
scheme had already dropped.
|
|
174
|
+
"""
|
|
175
|
+
Program = apps.get_model("projects", "Program")
|
|
176
|
+
ProgramMembership = apps.get_model("access", "ProgramMembership")
|
|
177
|
+
ProgramSyncSequence = apps.get_model("sync", "ProgramSyncSequence")
|
|
178
|
+
|
|
179
|
+
# (model, creation-timestamp field). The two models do not share a field name.
|
|
180
|
+
ordered_tables = ((Program, "created_at"), (ProgramMembership, "joined_at"))
|
|
181
|
+
|
|
182
|
+
# Start above the old scale so no pre-upgrade client cursor can sit above a row.
|
|
183
|
+
highest_old = 0
|
|
184
|
+
for model, _ in ordered_tables:
|
|
185
|
+
for value in model.objects.values_list("server_version", flat=True):
|
|
186
|
+
highest_old = max(highest_old, value or 0)
|
|
187
|
+
|
|
188
|
+
seq = highest_old
|
|
189
|
+
# Soft-deleted rows are renumbered too — they are tombstones the delta must
|
|
190
|
+
# still deliver, selected by the same sync_seq__gt=since filter.
|
|
191
|
+
for model, created_field in ordered_tables:
|
|
192
|
+
for pk in model.objects.order_by(created_field, "id").values_list("pk", flat=True):
|
|
193
|
+
seq += 1
|
|
194
|
+
model.objects.filter(pk=pk).update(sync_seq=seq)
|
|
195
|
+
|
|
196
|
+
# The allocator must sit at or above every row it will be compared against, or
|
|
197
|
+
# the first watermark read would publish a checkpoint below live rows.
|
|
198
|
+
ProgramSyncSequence.objects.update_or_create(pk=1, defaults={"value": seq})
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def unseed_program_sync_seq(apps: Any, schema_editor: Any) -> None:
|
|
202
|
+
"""Reverse :func:`seed_program_sync_seq` — return both tables to ``sync_seq = 0``.
|
|
203
|
+
|
|
204
|
+
The counter row is dropped with the table by the schema operation, so only the
|
|
205
|
+
stamped rows need clearing.
|
|
206
|
+
"""
|
|
207
|
+
for label, name in (("projects", "Program"), ("access", "ProgramMembership")):
|
|
208
|
+
apps.get_model(label, name).objects.update(sync_seq=0)
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
"""Broadcast helper — sends events to a project's WebSocket group.
|
|
2
|
+
|
|
3
|
+
All mutations and CPM completions call broadcast_board_event() wrapped in
|
|
4
|
+
transaction.on_commit() so that clients never receive an event for a write
|
|
5
|
+
that was subsequently rolled back.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from datetime import UTC
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from asgiref.sync import async_to_sync
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
# Wire-protocol version for the ``board.event`` envelope (#1325). A bare integer
|
|
19
|
+
# down-payment reserved *before* 0.3 ships and freezes 60+ ``event_type`` strings:
|
|
20
|
+
# once a client builds against the envelope, adding a versioning handshake later
|
|
21
|
+
# would be a breaking change, whereas a field that is already present can grow a
|
|
22
|
+
# meaning without one. Clients that don't read it are unaffected; future clients
|
|
23
|
+
# can branch on it. Bump only on a backward-incompatible envelope change.
|
|
24
|
+
WS_PROTOCOL_VERSION = 1
|
|
25
|
+
|
|
26
|
+
# Event types that are deliberately NOT persisted to the BoardEvent replay buffer
|
|
27
|
+
# (ADR-0236, #321). Everything else is persisted so a reconnecting client can
|
|
28
|
+
# replay it via ``?since=``. A *denylist* (not an allowlist) is the fail-safe
|
|
29
|
+
# default: every mutation event maps to an idempotent client cache-invalidation,
|
|
30
|
+
# so replaying one is always safe, and a newly-added mutation event should be
|
|
31
|
+
# replayable without anyone remembering to register it. Two reasons to exclude:
|
|
32
|
+
#
|
|
33
|
+
# 1. **Ephemeral** high-frequency live progress / presence pings — a stale
|
|
34
|
+
# progress bar or a presence ping for a since-departed peer carries no value
|
|
35
|
+
# on replay.
|
|
36
|
+
# 2. **Post-delete** — ``project_hard_deleted`` fires from an on_commit callback
|
|
37
|
+
# *after* the project row (and its cascade-deleted buffer rows) are gone, so
|
|
38
|
+
# persisting a BoardEvent for it would dangle the project FK. The DEFERRABLE
|
|
39
|
+
# FK check surfaces at COMMIT, past ``_persist_board_event``'s try/except, so
|
|
40
|
+
# it cannot be swallowed — it must not be attempted. A hard-deleted project
|
|
41
|
+
# has no replay value anyway (a reconnecting member is evicted or 404s).
|
|
42
|
+
# 3. **Program-scoped** — the ``program_*`` lifecycle events (split / deleted /
|
|
43
|
+
# closed / reopened / sponsorship_transferred) fan out on a *program*'s WS
|
|
44
|
+
# group but pass the **Program** pk as ``project_id``. A Program pk is never a
|
|
45
|
+
# ``projects.Project`` pk, so persisting a BoardEvent for one always violates
|
|
46
|
+
# the Project FK at COMMIT — an unhandled IntegrityError → 500 for an
|
|
47
|
+
# otherwise-valid request (#2126). Like ``project_hard_deleted``, the FK check
|
|
48
|
+
# surfaces past ``_persist_board_event``'s try/except, so the persist must not
|
|
49
|
+
# be attempted; these events are live-only signals with no project replay row.
|
|
50
|
+
DONT_PERSIST_EVENT_TYPES = frozenset(
|
|
51
|
+
{
|
|
52
|
+
"presence_join",
|
|
53
|
+
"presence_leave",
|
|
54
|
+
"task_run_started",
|
|
55
|
+
"task_run_progress",
|
|
56
|
+
"task_run_completed",
|
|
57
|
+
"task_run_failed",
|
|
58
|
+
"task_run_cancelled",
|
|
59
|
+
"project_hard_deleted",
|
|
60
|
+
"program_split",
|
|
61
|
+
"program_deleted",
|
|
62
|
+
"program_closed",
|
|
63
|
+
"program_reopened",
|
|
64
|
+
"program_sponsorship_transferred",
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _group_name(project_id: str) -> str:
|
|
70
|
+
"""Return the channel layer group name for a project."""
|
|
71
|
+
return f"project_{project_id}"
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _record_broadcast_metric() -> None:
|
|
75
|
+
"""Best-effort increment of the WS broadcast counter (#1900).
|
|
76
|
+
|
|
77
|
+
Imported lazily (matching this module's lazy-import convention) and wrapped so a
|
|
78
|
+
telemetry error can never touch the latency-sensitive broadcast path — the
|
|
79
|
+
broadcast is best-effort, and so is its metric.
|
|
80
|
+
"""
|
|
81
|
+
try:
|
|
82
|
+
from trueppm_api.apps.observability.otel import metrics
|
|
83
|
+
|
|
84
|
+
metrics.record_ws_broadcast()
|
|
85
|
+
except Exception:
|
|
86
|
+
logger.debug("broadcast metric increment skipped", exc_info=True)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _persist_board_event(project_id: str, event_type: str, payload: dict[str, Any]) -> int | None:
|
|
90
|
+
"""Persist a replayable event to the BoardEvent buffer; return its sequence.
|
|
91
|
+
|
|
92
|
+
The sequence is the row's ``BigAutoField`` PK — globally monotonic, assigned
|
|
93
|
+
atomically by Postgres, so it survives concurrent commits with no lost or
|
|
94
|
+
duplicate values (ADR-0236). Returns ``None`` for non-persisted events
|
|
95
|
+
(``DONT_PERSIST_EVENT_TYPES``) and on any DB failure: best-effort exactly like the
|
|
96
|
+
broadcast itself, so a failed insert logs and is swallowed — the live event
|
|
97
|
+
still goes out (without a ``seq``) and the client recovers the gap via a
|
|
98
|
+
``resync_required`` on its next reconnect.
|
|
99
|
+
|
|
100
|
+
Callers already run this inside a ``transaction.on_commit`` callback, so the
|
|
101
|
+
INSERT happens post-commit in autocommit — the row is the durable truth, the
|
|
102
|
+
broadcast is the best-effort echo.
|
|
103
|
+
"""
|
|
104
|
+
if event_type in DONT_PERSIST_EVENT_TYPES:
|
|
105
|
+
return None
|
|
106
|
+
|
|
107
|
+
from django.db import DatabaseError
|
|
108
|
+
|
|
109
|
+
from trueppm_api.apps.sync.models import BoardEvent
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
row = BoardEvent.objects.create(
|
|
113
|
+
project_id=project_id, event_type=event_type, payload=payload
|
|
114
|
+
)
|
|
115
|
+
except DatabaseError:
|
|
116
|
+
logger.exception(
|
|
117
|
+
"broadcast: failed to persist BoardEvent %s for project %s", event_type, project_id
|
|
118
|
+
)
|
|
119
|
+
return None
|
|
120
|
+
return row.pk
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _board_message(
|
|
124
|
+
event_type: str, payload: dict[str, Any], seq: int | None = None
|
|
125
|
+
) -> dict[str, Any]:
|
|
126
|
+
"""Build the ``board.event`` channel-layer envelope consumers expect.
|
|
127
|
+
|
|
128
|
+
Shared by the sync and async broadcast helpers so the wire shape stays in
|
|
129
|
+
one place — ``ProjectConsumer.board_event`` reads ``event_type``/``payload``
|
|
130
|
+
off the top level. The ``protocol_version`` field is the single source of the wire
|
|
131
|
+
version for every board event (#1325).
|
|
132
|
+
|
|
133
|
+
``seq`` is the BoardEvent sequence for a persisted (replayable) event, or
|
|
134
|
+
``None`` for an ephemeral event that was not buffered (ADR-0236). It is
|
|
135
|
+
additive: a client that ignores it behaves exactly as before, so
|
|
136
|
+
``protocol_version`` stays ``1``.
|
|
137
|
+
"""
|
|
138
|
+
return {
|
|
139
|
+
"type": "board.event",
|
|
140
|
+
"protocol_version": WS_PROTOCOL_VERSION,
|
|
141
|
+
"event_type": event_type,
|
|
142
|
+
"payload": payload,
|
|
143
|
+
"seq": seq,
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def broadcast_task_updated(
|
|
148
|
+
project_id: str,
|
|
149
|
+
*,
|
|
150
|
+
task_id: str,
|
|
151
|
+
changed_fields: list[str],
|
|
152
|
+
version: int | None,
|
|
153
|
+
actor_id: str | None,
|
|
154
|
+
) -> None:
|
|
155
|
+
"""Broadcast a field-level ``task_updated`` delta (ADR-0152, #327).
|
|
156
|
+
|
|
157
|
+
Assembles the standard mutation-delta payload so every task-mutation call site
|
|
158
|
+
emits the same shape: collaborators learn *which* fields changed, at what
|
|
159
|
+
``server_version``, and *who* changed them — without the field *values* ever
|
|
160
|
+
crossing the wire. Values are deliberately omitted because task fields are
|
|
161
|
+
role-gated (ADR-0104 nulls ``story_points`` below the velocity audience, cost
|
|
162
|
+
fields are gated, etc.); a client that needs the new values re-reads the task
|
|
163
|
+
through the serializer, which re-applies per-user gating. ``actor_id`` lets the
|
|
164
|
+
originating client suppress its own echo and avoid clobbering an in-flight
|
|
165
|
+
optimistic update.
|
|
166
|
+
|
|
167
|
+
Like :func:`broadcast_board_event`, this is best-effort and must be wrapped in
|
|
168
|
+
``transaction.on_commit`` by the caller. The ``id`` key is retained for
|
|
169
|
+
backward compatibility with consumers that only read it.
|
|
170
|
+
"""
|
|
171
|
+
broadcast_board_event(
|
|
172
|
+
project_id,
|
|
173
|
+
"task_updated",
|
|
174
|
+
{
|
|
175
|
+
"id": task_id,
|
|
176
|
+
"changed_fields": sorted(changed_fields),
|
|
177
|
+
"version": version,
|
|
178
|
+
"actor_id": actor_id,
|
|
179
|
+
"ts": _utcnow_iso(),
|
|
180
|
+
},
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _utcnow_iso() -> str:
|
|
185
|
+
"""Current UTC time as an ISO-8601 string (factored out for test stubbing)."""
|
|
186
|
+
from datetime import datetime
|
|
187
|
+
|
|
188
|
+
return datetime.now(UTC).isoformat()
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def broadcast_board_event(
|
|
192
|
+
project_id: str,
|
|
193
|
+
event_type: str,
|
|
194
|
+
payload: dict[str, Any],
|
|
195
|
+
) -> None:
|
|
196
|
+
"""Send a JSON event to all WebSocket clients connected to a project group.
|
|
197
|
+
|
|
198
|
+
Synchronous helper for Django views and Celery tasks. Uses asgiref's
|
|
199
|
+
async_to_sync adapter (the official Channels approach) rather than
|
|
200
|
+
asyncio.run() so the existing thread's event-loop context is reused
|
|
201
|
+
instead of a fresh one being booted per call.
|
|
202
|
+
|
|
203
|
+
Durability: this broadcast is **best-effort by design** and safe to lose.
|
|
204
|
+
Every event it carries is durably persisted in the DB before the broadcast
|
|
205
|
+
is scheduled (callers wrap this in ``transaction.on_commit``), and clients
|
|
206
|
+
reconcile by pulling the sync delta on (re)connect. If the channel layer is
|
|
207
|
+
down at commit time the event is dropped and the client recovers it on its
|
|
208
|
+
next delta fetch — nothing durable is lost. This is why ``on_commit``
|
|
209
|
+
callbacks that *only* broadcast need no outbox row, whereas callbacks that
|
|
210
|
+
dispatch Celery work must commit one first. See
|
|
211
|
+
``docs/durability/on-commit-audit.md`` (#659).
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
project_id: UUID string of the project whose group to broadcast to.
|
|
215
|
+
event_type: Short identifier for the event, e.g. "task_created" or
|
|
216
|
+
"cpm_complete".
|
|
217
|
+
payload: JSON-serializable dict with event-specific data.
|
|
218
|
+
"""
|
|
219
|
+
from channels.layers import get_channel_layer
|
|
220
|
+
|
|
221
|
+
channel_layer = get_channel_layer()
|
|
222
|
+
if channel_layer is None:
|
|
223
|
+
logger.warning("broadcast_board_event: no channel layer configured, skipping broadcast")
|
|
224
|
+
return
|
|
225
|
+
|
|
226
|
+
# Persist the replay-buffer row first so its sequence can ride the live
|
|
227
|
+
# payload (ADR-0236). Best-effort: a persist failure yields seq=None and the
|
|
228
|
+
# event still broadcasts.
|
|
229
|
+
seq = _persist_board_event(project_id, event_type, payload)
|
|
230
|
+
|
|
231
|
+
group = _group_name(project_id)
|
|
232
|
+
try:
|
|
233
|
+
async_to_sync(channel_layer.group_send)(group, _board_message(event_type, payload, seq))
|
|
234
|
+
except Exception:
|
|
235
|
+
logger.exception("broadcast_board_event: failed to send %s to group %s", event_type, group)
|
|
236
|
+
else:
|
|
237
|
+
# Count the successful fan-out (#1900). A strict no-op unless telemetry is
|
|
238
|
+
# on, self-guarding, and in the ``else`` branch so a failed send is not
|
|
239
|
+
# counted — this only observes the broadcast, never alters its semantics.
|
|
240
|
+
_record_broadcast_metric()
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
async def abroadcast_board_event(
|
|
244
|
+
project_id: str,
|
|
245
|
+
event_type: str,
|
|
246
|
+
payload: dict[str, Any],
|
|
247
|
+
) -> None:
|
|
248
|
+
"""Async-native broadcast for callers already on an event loop.
|
|
249
|
+
|
|
250
|
+
The synchronous ``broadcast_board_event`` wraps ``group_send`` in
|
|
251
|
+
``async_to_sync``, which raises ``RuntimeError`` when called from a thread
|
|
252
|
+
that already runs an event loop — exactly the case inside a Channels
|
|
253
|
+
consumer. Async callers (e.g. ``ProjectConsumer`` presence join/leave) must
|
|
254
|
+
``await`` this instead so ``group_send`` is awaited directly.
|
|
255
|
+
|
|
256
|
+
Same best-effort durability contract as the sync helper: a channel-layer
|
|
257
|
+
failure is logged and swallowed, never raised — clients reconcile on
|
|
258
|
+
reconnect (#958).
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
project_id: UUID string of the project whose group to broadcast to.
|
|
262
|
+
event_type: Short identifier for the event, e.g. "presence_join".
|
|
263
|
+
payload: JSON-serializable dict with event-specific data.
|
|
264
|
+
"""
|
|
265
|
+
from channels.layers import get_channel_layer
|
|
266
|
+
|
|
267
|
+
channel_layer = get_channel_layer()
|
|
268
|
+
if channel_layer is None:
|
|
269
|
+
logger.warning("abroadcast_board_event: no channel layer configured, skipping broadcast")
|
|
270
|
+
return
|
|
271
|
+
|
|
272
|
+
# Persist the replay-buffer row (ADR-0236). The only async callers today are
|
|
273
|
+
# presence join/leave, which are ephemeral and short-circuit to seq=None
|
|
274
|
+
# without touching the DB; the database_sync_to_async hop only runs if a future
|
|
275
|
+
# async caller broadcasts a replayable event, keeping this path symmetric with
|
|
276
|
+
# the sync helper rather than silently dropping such an event from the buffer.
|
|
277
|
+
if event_type in DONT_PERSIST_EVENT_TYPES:
|
|
278
|
+
seq = None
|
|
279
|
+
else:
|
|
280
|
+
from channels.db import database_sync_to_async
|
|
281
|
+
|
|
282
|
+
seq = await database_sync_to_async(_persist_board_event)(project_id, event_type, payload)
|
|
283
|
+
|
|
284
|
+
group = _group_name(project_id)
|
|
285
|
+
try:
|
|
286
|
+
await channel_layer.group_send(group, _board_message(event_type, payload, seq))
|
|
287
|
+
except Exception:
|
|
288
|
+
logger.exception("abroadcast_board_event: failed to send %s to group %s", event_type, group)
|
|
289
|
+
else:
|
|
290
|
+
# Count the successful fan-out (#1900) — same contract as the sync helper.
|
|
291
|
+
_record_broadcast_metric()
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def evict_project_connection(project_id: str, user_id: str) -> None:
|
|
295
|
+
"""Push a ``connection.evict`` to a project's board WS group (#813).
|
|
296
|
+
|
|
297
|
+
Project membership is checked only at ``websocket_connect``; once accepted, a
|
|
298
|
+
socket keeps receiving every ``board_event`` until it disconnects. When a
|
|
299
|
+
``ProjectMembership`` is soft-deleted or demoted below ``Role.MEMBER``, this
|
|
300
|
+
evicts the user's *live* sockets so they stop receiving real-time project data
|
|
301
|
+
immediately (the reconnect path is already gated — this closes the active-
|
|
302
|
+
connection gap, the analog of #419). Each consumer whose authenticated user
|
|
303
|
+
matches ``user_id`` closes with code 4003; sockets for other users ignore it.
|
|
304
|
+
|
|
305
|
+
Best-effort like ``broadcast_board_event`` — callers defer it with
|
|
306
|
+
``transaction.on_commit``.
|
|
307
|
+
"""
|
|
308
|
+
from channels.layers import get_channel_layer
|
|
309
|
+
|
|
310
|
+
channel_layer = get_channel_layer()
|
|
311
|
+
if channel_layer is None:
|
|
312
|
+
logger.warning("evict_project_connection: no channel layer configured, skipping")
|
|
313
|
+
return
|
|
314
|
+
|
|
315
|
+
message = {"type": "connection.evict", "user_id": str(user_id)}
|
|
316
|
+
group = _group_name(project_id)
|
|
317
|
+
try:
|
|
318
|
+
async_to_sync(channel_layer.group_send)(group, message)
|
|
319
|
+
except Exception:
|
|
320
|
+
logger.exception("evict_project_connection: failed to evict %s from %s", user_id, group)
|