trueppm-api 0.4.0__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 +27 -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 +346 -0
- trueppm_api/apps/access/management/commands/migrate_locked.py +96 -0
- trueppm_api/apps/access/management/commands/revoke_api_tokens.py +483 -0
- trueppm_api/apps/access/migrations/0001_initial.py +61 -0
- trueppm_api/apps/access/migrations/0001_squashed_0012_programmembership_role_title.py +184 -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_squashed_0019_release_0_4.py +302 -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 +143 -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/minter_role.py +38 -0
- trueppm_api/apps/access/models.py +588 -0
- trueppm_api/apps/access/permissions.py +3263 -0
- trueppm_api/apps/access/serializers.py +1007 -0
- trueppm_api/apps/access/services.py +878 -0
- trueppm_api/apps/access/signals.py +171 -0
- trueppm_api/apps/access/tasks.py +50 -0
- trueppm_api/apps/access/throttles.py +96 -0
- trueppm_api/apps/access/urls.py +191 -0
- trueppm_api/apps/access/views.py +1965 -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/0001_squashed_0003_release_0_4.py +344 -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 +109 -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/0001_squashed_0003_release_0_4.py +90 -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 +459 -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 +178 -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 +863 -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 +597 -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_squashed_0014_release_0_4.py +225 -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/0014_tasklink_created_by.py +27 -0
- trueppm_api/apps/integrations/migrations/__init__.py +0 -0
- trueppm_api/apps/integrations/models.py +635 -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 +528 -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 +734 -0
- trueppm_api/apps/integrations/throttles.py +165 -0
- trueppm_api/apps/integrations/urls.py +77 -0
- trueppm_api/apps/integrations/views.py +1158 -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 +280 -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 +551 -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 +1276 -0
- trueppm_api/apps/msproject/serializers.py +136 -0
- trueppm_api/apps/msproject/services.py +58 -0
- trueppm_api/apps/msproject/tasks.py +439 -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 +462 -0
- trueppm_api/apps/notifications/email_backend.py +350 -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 +299 -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_squashed_0011_release_0_4.py +204 -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 +1148 -0
- trueppm_api/apps/notifications/receivers.py +155 -0
- trueppm_api/apps/notifications/schema_migrations.py +56 -0
- trueppm_api/apps/notifications/serializers.py +1094 -0
- trueppm_api/apps/notifications/services.py +1489 -0
- trueppm_api/apps/notifications/tasks.py +762 -0
- trueppm_api/apps/notifications/throttles.py +98 -0
- trueppm_api/apps/notifications/urls.py +97 -0
- trueppm_api/apps/notifications/views.py +919 -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 +566 -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 +993 -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 +537 -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_squashed_0009_release_0_4.py +128 -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 +541 -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 +458 -0
- trueppm_api/apps/projects/backfill.py +459 -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 +212 -0
- trueppm_api/apps/projects/config_notice.py +1330 -0
- trueppm_api/apps/projects/custom_field_values.py +266 -0
- trueppm_api/apps/projects/decisions_services.py +132 -0
- trueppm_api/apps/projects/decisions_views.py +97 -0
- trueppm_api/apps/projects/estimation_scale.py +126 -0
- trueppm_api/apps/projects/export_bundle.py +423 -0
- trueppm_api/apps/projects/fixtures/seeds/atlas-platform-launch.json +4278 -0
- trueppm_api/apps/projects/fixtures/seeds/aurora-mobile-app.json +2001 -0
- trueppm_api/apps/projects/fixtures/seeds/bayside-civic-center.json +1827 -0
- trueppm_api/apps/projects/fixtures/seeds/ga-launch.json +1909 -0
- trueppm_api/apps/projects/fixtures/seeds/helios-crm-replacement.json +1410 -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/key_views.py +468 -0
- trueppm_api/apps/projects/keys.py +205 -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 +514 -0
- trueppm_api/apps/projects/management/commands/seed_capacity.py +406 -0
- trueppm_api/apps/projects/management/commands/seed_integration_fixtures.py +192 -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/migration_helpers.py +75 -0
- trueppm_api/apps/projects/migrations/0001_initial.py +208 -0
- trueppm_api/apps/projects/migrations/0001_squashed_0094_project_decisions_policy.py +5659 -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 +59 -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 +75 -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 +76 -0
- trueppm_api/apps/projects/migrations/0106_boardsavedview_schema_version.py +20 -0
- trueppm_api/apps/projects/migrations/0106_squashed_0158_release_0_4.py +2357 -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/0154_object_keys.py +184 -0
- trueppm_api/apps/projects/migrations/0155_task_milestone_at_day_end.py +20 -0
- trueppm_api/apps/projects/migrations/0156_program_import_job_replaced_project_ids.py +17 -0
- trueppm_api/apps/projects/migrations/0157_forecastsnapshot_cpm_finish_at_day_start.py +17 -0
- trueppm_api/apps/projects/migrations/0158_baselinetask_finish_at_day_start.py +17 -0
- trueppm_api/apps/projects/migrations/__init__.py +0 -0
- trueppm_api/apps/projects/models.py +9209 -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 +903 -0
- trueppm_api/apps/projects/program_schedule.py +767 -0
- trueppm_api/apps/projects/program_views.py +3445 -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 +492 -0
- trueppm_api/apps/projects/schemas/seed_v2.json +891 -0
- trueppm_api/apps/projects/seed/__init__.py +61 -0
- trueppm_api/apps/projects/seed/demo_landing_overlay.py +532 -0
- trueppm_api/apps/projects/seed/exporter.py +1717 -0
- trueppm_api/apps/projects/seed/forecast_backfill.py +247 -0
- trueppm_api/apps/projects/seed/importer.py +2764 -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 +128 -0
- trueppm_api/apps/projects/seed/replay.py +1695 -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 +1169 -0
- trueppm_api/apps/projects/serializers.py +12904 -0
- trueppm_api/apps/projects/services.py +7525 -0
- trueppm_api/apps/projects/share_serializers.py +142 -0
- trueppm_api/apps/projects/share_services.py +373 -0
- trueppm_api/apps/projects/share_views.py +395 -0
- trueppm_api/apps/projects/sharing_settings.py +187 -0
- trueppm_api/apps/projects/signal_privacy_services.py +910 -0
- trueppm_api/apps/projects/signal_privacy_views.py +485 -0
- trueppm_api/apps/projects/signals.py +132 -0
- trueppm_api/apps/projects/slip_conflict.py +288 -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 +1244 -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 +1979 -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 +261 -0
- trueppm_api/apps/projects/template_views.py +829 -0
- trueppm_api/apps/projects/throttles.py +311 -0
- trueppm_api/apps/projects/urls.py +1016 -0
- trueppm_api/apps/projects/utilization.py +909 -0
- trueppm_api/apps/projects/views.py +21225 -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/0014_squashed_0015_release_0_4.py +47 -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 +1752 -0
- trueppm_api/apps/scheduling/__init__.py +0 -0
- trueppm_api/apps/scheduling/apps.py +153 -0
- trueppm_api/apps/scheduling/calendars.py +144 -0
- trueppm_api/apps/scheduling/deadletter.py +91 -0
- trueppm_api/apps/scheduling/finish_reading.py +198 -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/mc_admission.py +181 -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/0010_squashed_0016_release_0_4.py +106 -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/0015_projectforecastsnapshot_cpm_finish_at_day_start.py +17 -0
- trueppm_api/apps/scheduling/migrations/0016_montecarlorun_finish_readings.py +32 -0
- trueppm_api/apps/scheduling/migrations/__init__.py +0 -0
- trueppm_api/apps/scheduling/models.py +542 -0
- trueppm_api/apps/scheduling/receivers.py +160 -0
- trueppm_api/apps/scheduling/risk_premium.py +334 -0
- trueppm_api/apps/scheduling/serializers.py +723 -0
- trueppm_api/apps/scheduling/services.py +1251 -0
- trueppm_api/apps/scheduling/signals.py +56 -0
- trueppm_api/apps/scheduling/tasks.py +2248 -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 +2908 -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 +446 -0
- trueppm_api/apps/sso/services.py +1473 -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 +369 -0
- trueppm_api/apps/sync/conflict.py +262 -0
- trueppm_api/apps/sync/consumers.py +548 -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/0003_squashed_0005_release_0_4.py +77 -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/0006_boardevent_per_project_seq.py +77 -0
- trueppm_api/apps/sync/migrations/__init__.py +0 -0
- trueppm_api/apps/sync/models.py +272 -0
- trueppm_api/apps/sync/pagination.py +231 -0
- trueppm_api/apps/sync/sequence.py +488 -0
- trueppm_api/apps/sync/serializers.py +688 -0
- trueppm_api/apps/sync/tasks.py +260 -0
- trueppm_api/apps/sync/throttles.py +175 -0
- trueppm_api/apps/sync/upload.py +556 -0
- trueppm_api/apps/sync/urls.py +17 -0
- trueppm_api/apps/sync/views.py +957 -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 +159 -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/0004_backfill_default_teams_on_upgrade.py +49 -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 +440 -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/0001_squashed_0004_release_0_4.py +167 -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 +211 -0
- trueppm_api/apps/timetracking/urls.py +47 -0
- trueppm_api/apps/timetracking/views.py +597 -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 +279 -0
- trueppm_api/apps/webhooks/migrations/0001_initial.py +146 -0
- trueppm_api/apps/webhooks/migrations/0001_squashed_0007_sprint_lifecycle_events.py +249 -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 +100 -0
- trueppm_api/apps/webhooks/migrations/0010_alter_webhook_disabled_at.py +21 -0
- trueppm_api/apps/webhooks/migrations/0011_webhookeditor.py +48 -0
- trueppm_api/apps/webhooks/migrations/0012_alter_webhook_disabled_at.py +21 -0
- trueppm_api/apps/webhooks/migrations/__init__.py +0 -0
- trueppm_api/apps/webhooks/models.py +437 -0
- trueppm_api/apps/webhooks/serializers.py +326 -0
- trueppm_api/apps/webhooks/tasks.py +680 -0
- trueppm_api/apps/webhooks/urls.py +22 -0
- trueppm_api/apps/webhooks/views.py +533 -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 +371 -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 +254 -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_squashed_0027_release_0_4.py +199 -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 +967 -0
- trueppm_api/apps/workspace/signals.py +37 -0
- trueppm_api/apps/workspace/tasks.py +616 -0
- trueppm_api/apps/workspace/urls.py +113 -0
- trueppm_api/apps/workspace/views.py +1581 -0
- trueppm_api/asgi.py +49 -0
- trueppm_api/celery.py +40 -0
- trueppm_api/core/__init__.py +0 -0
- trueppm_api/core/admin_site.py +206 -0
- trueppm_api/core/auth_views.py +934 -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/demo_read_only.py +314 -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 +1221 -0
- trueppm_api/core/password_reset.py +513 -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 +1034 -0
- trueppm_api/core/storage_config.py +105 -0
- trueppm_api/core/text_decode.py +168 -0
- trueppm_api/core/throttling.py +514 -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/core/worker_selfheal.py +205 -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 +2605 -0
- trueppm_api/settings/dev.py +296 -0
- trueppm_api/settings/prod.py +265 -0
- trueppm_api/urls.py +217 -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.0.dist-info/METADATA +273 -0
- trueppm_api-0.4.0.dist-info/RECORD +799 -0
- trueppm_api-0.4.0.dist-info/WHEEL +4 -0
- trueppm_api-0.4.0.dist-info/licenses/LICENSE +201 -0
- trueppm_api-0.4.0.dist-info/licenses/NOTICE +7 -0
- trueppm_api-0.4.0.dist-info/licenses/NOTICES +104 -0
- trueppm_api-0.4.0.dist-info/licenses/licenses/GPL-3.0.txt +674 -0
- trueppm_api-0.4.0.dist-info/licenses/licenses/LGPL-3.0.txt +165 -0
- trueppm_api-0.4.0.dist-info/licenses/licenses/MPL-2.0.txt +373 -0
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
"""Per-project monotonic sequence backing the sync delta cursor (ADR-0686, #2491).
|
|
2
|
+
|
|
3
|
+
``Project.last_sync_version`` is the allocator for a project's ``sync_seq``
|
|
4
|
+
sequence. Every synced write draws the next value in one statement::
|
|
5
|
+
|
|
6
|
+
UPDATE projects_project SET last_sync_version = last_sync_version + 1
|
|
7
|
+
WHERE id = %s RETURNING last_sync_version
|
|
8
|
+
|
|
9
|
+
and stamps it on the row's ``sync_seq``. Because every synced row in a project is
|
|
10
|
+
drawn from the same counter, the values are totally ordered within the project,
|
|
11
|
+
``MAX`` is a meaningful checkpoint, and the delta filter ``sync_seq__gt=since`` is
|
|
12
|
+
correct.
|
|
13
|
+
|
|
14
|
+
This replaces the ADR-0142 watermark *receivers*, which cached
|
|
15
|
+
``MAX(server_version)`` on the same column. That cache was faithful; the thing it
|
|
16
|
+
cached was not a valid ordering — ``server_version`` counts a single row's saves,
|
|
17
|
+
so comparing it against a project-wide maximum let a frequently-edited row raise
|
|
18
|
+
the checkpoint above every other row's counter and drop those rows from the delta
|
|
19
|
+
permanently (#2491). ``server_version`` keeps its per-row meaning (optimistic
|
|
20
|
+
lock, and the 1:1 history-row correspondence the ADR-0217 field-level merge
|
|
21
|
+
counts on); ``sync_seq`` is the replication cursor.
|
|
22
|
+
|
|
23
|
+
**The project row lock is load-bearing.** Allocation ``UPDATE``s the project row
|
|
24
|
+
and holds its write lock until commit, so concurrent synced writes to one project
|
|
25
|
+
serialize on allocation and commit order equals allocation order. Without that a
|
|
26
|
+
transaction could allocate 100, a later one allocate 101 and commit first, and a
|
|
27
|
+
pull between the two commits would report 101 and permanently skip 100 — this
|
|
28
|
+
same bug, reintroduced as a race. Under ``READ COMMITTED`` readers neither block
|
|
29
|
+
nor observe an uncommitted allocation, so a pull always reports a checkpoint whose
|
|
30
|
+
rows are all committed.
|
|
31
|
+
|
|
32
|
+
For a single write this adds no contention ADR-0142 did not already have: its
|
|
33
|
+
receivers issued ``UPDATE projects_project SET last_sync_version = Greatest(…)``
|
|
34
|
+
on every synced save, taking the same lock. For a *batch* the lock is held
|
|
35
|
+
longer. The old ``coalesce_watermark_bumps`` deferred its one UPDATE to context
|
|
36
|
+
exit, so the project row was locked only for the tail of the transaction; the
|
|
37
|
+
allocator cannot defer, because it must return a value to stamp on the first row.
|
|
38
|
+
Two concurrent uploads to the same project therefore serialize across the whole
|
|
39
|
+
apply rather than just its tail. That is the price of an ordered cursor — the
|
|
40
|
+
ordering is exactly what the lock buys — but it is a real change, not a wash.
|
|
41
|
+
|
|
42
|
+
``Dependency`` and ``TaskRelation`` carry no project FK, but they *are* delivered
|
|
43
|
+
as their own collections, so they allocate through their edge's owning task
|
|
44
|
+
(``predecessor`` / ``source``) exactly like any other synced row. They must: the
|
|
45
|
+
delta filters each collection on its own ``sync_seq``, so a row that never
|
|
46
|
+
allocates stays at 0 and ``sync_seq__gt=since`` never returns it — not even on a
|
|
47
|
+
cold start with ``since=0``.
|
|
48
|
+
|
|
49
|
+
Deliberately *not* allocated: every ``VersionedModel`` outside the sync union
|
|
50
|
+
(``PulseResponse``, ``RetroBoardItem``, ``BacklogItem``, ``ApiToken``, resources,
|
|
51
|
+
teams, …). They keep ``sync_seq = 0``; nothing reads it for them.
|
|
52
|
+
|
|
53
|
+
CPM output writes use ``bulk_update`` (ADR-0091) — no ``save()``, no allocation —
|
|
54
|
+
so recompute leaves the cursor untouched, matching the delta.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
from __future__ import annotations
|
|
58
|
+
|
|
59
|
+
import threading
|
|
60
|
+
from collections.abc import Callable, Iterable, Iterator
|
|
61
|
+
from contextlib import contextmanager
|
|
62
|
+
from typing import Any
|
|
63
|
+
|
|
64
|
+
from django.db import models
|
|
65
|
+
from django.db.models import F, Value
|
|
66
|
+
from django.db.models.functions import Greatest
|
|
67
|
+
|
|
68
|
+
#: A resolver maps a synced instance to the project ids whose sequence it draws
|
|
69
|
+
#: from. Direct FKs read the id off the instance (no query). Indirect ones return
|
|
70
|
+
#: a queryset that ``allocate`` materializes, costing one extra SELECT per save:
|
|
71
|
+
#: ``_next_seq`` needs a concrete id for its ``UPDATE … WHERE id = %s``, so unlike
|
|
72
|
+
#: the ADR-0142 receivers it replaces, the queryset cannot stay an IN-subquery.
|
|
73
|
+
Resolver = Callable[[Any], Iterable[Any]]
|
|
74
|
+
|
|
75
|
+
# Per-thread allocation cache for the coalesced-batch context (see
|
|
76
|
+
# ``coalesce_sync_seq``). ``None`` outside a batch, so a non-batch save allocates
|
|
77
|
+
# its own value exactly as before.
|
|
78
|
+
_batch_state = threading.local()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _batch_cache() -> dict[Any, int] | None:
|
|
82
|
+
return getattr(_batch_state, "active", None)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _next_seq(project_id: Any) -> int:
|
|
86
|
+
"""Draw the next value of ``project_id``'s sequence, or 0 if the row is gone.
|
|
87
|
+
|
|
88
|
+
Returns 0 when the ``UPDATE`` matches no row — the project was hard-deleted
|
|
89
|
+
concurrently, or (for a ``Project`` being INSERTed) does not exist yet. The
|
|
90
|
+
caller decides what that means; a 0 cursor is simply never delivered.
|
|
91
|
+
|
|
92
|
+
The statement lives in :func:`trueppm_api.core.db.increment_returning`, shared
|
|
93
|
+
with the ``server_version`` bump so the API has one raw-SQL site.
|
|
94
|
+
"""
|
|
95
|
+
from trueppm_api.apps.projects.models import Project
|
|
96
|
+
from trueppm_api.core.db import increment_returning
|
|
97
|
+
|
|
98
|
+
seq = increment_returning(Project, "last_sync_version", project_id)
|
|
99
|
+
return seq if seq is not None else 0
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def allocate_program_seq() -> int:
|
|
103
|
+
"""Draw the next value of the installation-wide program sequence (ADR-0747).
|
|
104
|
+
|
|
105
|
+
The program slice of the sync protocol has no owning row to sequence from the
|
|
106
|
+
way a task has its project: the accessible set is the per-user union of the
|
|
107
|
+
caller's programs, and those programs are independent. A scalar cursor over
|
|
108
|
+
per-program sequences would reintroduce the same defect one level up — a hot
|
|
109
|
+
program's sequence outrunning a cold one's and dropping the cold program's rows
|
|
110
|
+
from the delta permanently (#2498). One counter for every program-scoped write
|
|
111
|
+
is what makes the scalar cursor sound again.
|
|
112
|
+
|
|
113
|
+
Returns 0 if the singleton row is missing (a database predating the backfill
|
|
114
|
+
migration). A row left at ``sync_seq = 0`` is simply never delivered, so this
|
|
115
|
+
fails closed toward "not yet replicated" rather than toward a false checkpoint.
|
|
116
|
+
"""
|
|
117
|
+
from trueppm_api.apps.sync.models import ProgramSyncSequence
|
|
118
|
+
from trueppm_api.core.db import increment_returning
|
|
119
|
+
|
|
120
|
+
seq = increment_returning(ProgramSyncSequence, "value", ProgramSyncSequence.SINGLETON_PK)
|
|
121
|
+
return seq if seq is not None else 0
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _raise_to(project_ids: Iterable[Any], seq: int) -> None:
|
|
125
|
+
"""Raise every project's allocator to at least ``seq`` (monotonic)."""
|
|
126
|
+
from trueppm_api.apps.projects.models import Project
|
|
127
|
+
|
|
128
|
+
Project.objects.filter(pk__in=project_ids).update(
|
|
129
|
+
last_sync_version=Greatest(F("last_sync_version"), Value(seq))
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def allocate_for_projects(project_ids: list[Any]) -> int:
|
|
134
|
+
"""Return a cursor value valid for every project in ``project_ids``.
|
|
135
|
+
|
|
136
|
+
The single-owner case — every synced model except ``Calendar`` — is one
|
|
137
|
+
``UPDATE … RETURNING``.
|
|
138
|
+
|
|
139
|
+
A row owned by several projects (a ``Calendar`` shared by more than one) can
|
|
140
|
+
carry only one ``sync_seq``, so it draws from *every* owner and keeps the
|
|
141
|
+
maximum. The follow-up ``_raise_to`` is what preserves the invariant
|
|
142
|
+
``sync_seq <= last_sync_version`` for the owners whose draw came in lower —
|
|
143
|
+
without it the shared row would sit permanently above one project's
|
|
144
|
+
checkpoint and be redelivered on every single pull.
|
|
145
|
+
"""
|
|
146
|
+
if not project_ids:
|
|
147
|
+
return 0
|
|
148
|
+
# Sort before taking any lock. A multi-owner row locks several project rows in
|
|
149
|
+
# sequence, and the resolver's queryset has no ORDER BY — two concurrent saves
|
|
150
|
+
# of the same shared calendar could otherwise grab the same two rows in
|
|
151
|
+
# opposite orders and deadlock. A total order on the ids removes the cycle.
|
|
152
|
+
project_ids = sorted(project_ids, key=str)
|
|
153
|
+
batch = _batch_cache()
|
|
154
|
+
if batch is None:
|
|
155
|
+
seq = max(_next_seq(pid) for pid in project_ids)
|
|
156
|
+
else:
|
|
157
|
+
for pid in project_ids:
|
|
158
|
+
if pid not in batch:
|
|
159
|
+
batch[pid] = _next_seq(pid)
|
|
160
|
+
seq = max(batch[pid] for pid in project_ids)
|
|
161
|
+
if len(project_ids) > 1 and seq:
|
|
162
|
+
_raise_to(project_ids, seq)
|
|
163
|
+
if batch is not None:
|
|
164
|
+
# Keep the cache consistent with the watermarks just raised. A later
|
|
165
|
+
# row in the same batch owned only by a lower-drawing project would
|
|
166
|
+
# otherwise reuse a value now *below* that project's watermark — and
|
|
167
|
+
# a row at or below the checkpoint is never delivered.
|
|
168
|
+
for pid in project_ids:
|
|
169
|
+
batch[pid] = seq
|
|
170
|
+
return seq
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def allocate(instance: Any) -> None:
|
|
174
|
+
"""Stamp ``instance.sync_seq`` with a fresh cursor value from its owner(s).
|
|
175
|
+
|
|
176
|
+
A no-op for models outside the sync union and for a synced row whose owning
|
|
177
|
+
project cannot be resolved yet (a ``Calendar`` no project points at). Such a
|
|
178
|
+
row keeps ``sync_seq = 0`` and is not delivered until an owner appears — see
|
|
179
|
+
:func:`register_sequence_receivers`, which allocates a calendar the moment a
|
|
180
|
+
project links to it.
|
|
181
|
+
"""
|
|
182
|
+
# Program-scoped rows draw from the installation-wide sequence instead of a
|
|
183
|
+
# project's (ADR-0747) — they have no owning project, and the per-user
|
|
184
|
+
# accessible set makes a per-program sequence unusable under a scalar cursor.
|
|
185
|
+
if type(instance) in PROGRAM_SEQUENCE_MODELS:
|
|
186
|
+
seq = allocate_program_seq()
|
|
187
|
+
if seq:
|
|
188
|
+
instance.sync_seq = seq
|
|
189
|
+
return
|
|
190
|
+
|
|
191
|
+
resolver = OWNER_RESOLVERS.get(type(instance))
|
|
192
|
+
if resolver is None:
|
|
193
|
+
return
|
|
194
|
+
project_ids = list(resolver(instance))
|
|
195
|
+
if not project_ids:
|
|
196
|
+
return
|
|
197
|
+
seq = allocate_for_projects(project_ids)
|
|
198
|
+
if not seq:
|
|
199
|
+
return
|
|
200
|
+
instance.sync_seq = seq
|
|
201
|
+
if hasattr(instance, "last_sync_version") and instance.pk in project_ids:
|
|
202
|
+
# A project drawing from its own sequence: keep the in-memory allocator in
|
|
203
|
+
# step with the row the UPDATE just wrote. `last_sync_version` is excluded
|
|
204
|
+
# from save()'s update_fields, so the DB value is already correct — this
|
|
205
|
+
# only stops a caller reading a stale watermark off the saved instance.
|
|
206
|
+
instance.last_sync_version = seq
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def seed_project_sequence(project: Any) -> None:
|
|
210
|
+
"""Start a not-yet-INSERTed project's sequence at 1, in memory.
|
|
211
|
+
|
|
212
|
+
``Project`` is itself a synced row, but on the INSERT path there is no row to
|
|
213
|
+
``UPDATE`` — the allocator column and the cursor are written by the INSERT
|
|
214
|
+
itself. Called from ``VersionedModel.save()``.
|
|
215
|
+
"""
|
|
216
|
+
project.last_sync_version = 1
|
|
217
|
+
project.sync_seq = 1
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
@contextmanager
|
|
221
|
+
def coalesce_sync_seq() -> Iterator[None]:
|
|
222
|
+
"""Draw one cursor value per project for the whole block.
|
|
223
|
+
|
|
224
|
+
Rows written together may share a ``sync_seq``: the delta is ``> since`` and
|
|
225
|
+
the checkpoint is the maximum, so a batch is either wholly before or wholly
|
|
226
|
+
after any client checkpoint. Sharing keeps the #1527 optimization that stopped
|
|
227
|
+
a 500-row sync upload from issuing 500 ``UPDATE projects_project`` statements
|
|
228
|
+
— it now issues one per project instead of one per row.
|
|
229
|
+
|
|
230
|
+
The allocations run *inside* the caller's transaction, so a rollback discards
|
|
231
|
+
them along with the rows. Re-entrancy is a no-op: a nested call shares the
|
|
232
|
+
outermost cache.
|
|
233
|
+
"""
|
|
234
|
+
if _batch_cache() is not None:
|
|
235
|
+
yield
|
|
236
|
+
return
|
|
237
|
+
_batch_state.active = {}
|
|
238
|
+
try:
|
|
239
|
+
yield
|
|
240
|
+
finally:
|
|
241
|
+
_batch_state.active = None
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _resolvers() -> dict[type[models.Model], Resolver]:
|
|
245
|
+
"""Build the model → owning-project-ids map (imports are deferred to app-ready)."""
|
|
246
|
+
from trueppm_api.apps.access.models import ProjectMembership
|
|
247
|
+
from trueppm_api.apps.integrations.models import TaskLink
|
|
248
|
+
from trueppm_api.apps.projects.models import (
|
|
249
|
+
Calendar,
|
|
250
|
+
Dependency,
|
|
251
|
+
Label,
|
|
252
|
+
Project,
|
|
253
|
+
RetroActionItem,
|
|
254
|
+
Risk,
|
|
255
|
+
Sprint,
|
|
256
|
+
SprintRetro,
|
|
257
|
+
Task,
|
|
258
|
+
TaskRecurrenceRule,
|
|
259
|
+
TaskRelation,
|
|
260
|
+
TaskSuggestedAssignee,
|
|
261
|
+
)
|
|
262
|
+
from trueppm_api.apps.timetracking.models import TimeEntry
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
Project: lambda i: [i.pk],
|
|
266
|
+
Task: lambda i: [i.project_id],
|
|
267
|
+
ProjectMembership: lambda i: [i.project_id],
|
|
268
|
+
Risk: lambda i: [i.project_id],
|
|
269
|
+
Sprint: lambda i: [i.project_id],
|
|
270
|
+
# Label catalog (ADR-0400) — direct project FK. The TaskLabel through-row
|
|
271
|
+
# is deliberately absent: it has no cursor of its own, and attach/detach
|
|
272
|
+
# saves the Task, which allocates there instead.
|
|
273
|
+
Label: lambda i: [i.project_id],
|
|
274
|
+
# A calendar may be shared by several projects — draw from all of them.
|
|
275
|
+
Calendar: lambda i: list(
|
|
276
|
+
Project.objects.filter(calendar_id=i.pk).values_list("pk", flat=True)
|
|
277
|
+
),
|
|
278
|
+
SprintRetro: lambda i: Sprint.objects.filter(pk=i.sprint_id).values_list(
|
|
279
|
+
"project_id", flat=True
|
|
280
|
+
),
|
|
281
|
+
RetroActionItem: lambda i: SprintRetro.objects.filter(pk=i.retro_id).values_list(
|
|
282
|
+
"sprint__project_id", flat=True
|
|
283
|
+
),
|
|
284
|
+
TaskSuggestedAssignee: lambda i: Task.objects.filter(pk=i.task_id).values_list(
|
|
285
|
+
"project_id", flat=True
|
|
286
|
+
),
|
|
287
|
+
TaskLink: lambda i: Task.objects.filter(pk=i.task_id).values_list("project_id", flat=True),
|
|
288
|
+
# Graph edges have no project FK of their own. Both are delivered as their
|
|
289
|
+
# own sync collection filtered on their own `sync_seq`, so both must
|
|
290
|
+
# allocate — otherwise they sit at 0 and are never delivered at all. The
|
|
291
|
+
# owner is read off the endpoint the delta already scopes and orders by
|
|
292
|
+
# (`predecessor` / `source`), matching their composite indexes.
|
|
293
|
+
Dependency: lambda i: Task.objects.filter(pk=i.predecessor_id).values_list(
|
|
294
|
+
"project_id", flat=True
|
|
295
|
+
),
|
|
296
|
+
TaskRelation: lambda i: Task.objects.filter(pk=i.source_id).values_list(
|
|
297
|
+
"project_id", flat=True
|
|
298
|
+
),
|
|
299
|
+
TaskRecurrenceRule: lambda i: Task.objects.filter(pk=i.task_id).values_list(
|
|
300
|
+
"project_id", flat=True
|
|
301
|
+
),
|
|
302
|
+
# A time entry's owning project is reached through its task (ADR-0185 §6).
|
|
303
|
+
# Any user's entry advances the project cursor; the per-user sync filter
|
|
304
|
+
# lives in the delta query, not here — a cursor value the caller cannot
|
|
305
|
+
# see only costs a pull that returns nothing for that row.
|
|
306
|
+
TimeEntry: lambda i: Task.objects.filter(pk=i.task_id).values_list("project_id", flat=True),
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
#: Populated by :func:`register_sequence_receivers` at app-ready. Empty before
|
|
311
|
+
#: then, which makes ``allocate`` a no-op — correct during app loading.
|
|
312
|
+
OWNER_RESOLVERS: dict[type[models.Model], Resolver] = {}
|
|
313
|
+
|
|
314
|
+
#: Models whose ``sync_seq`` is drawn from the installation-wide program sequence
|
|
315
|
+
#: rather than an owning project's (ADR-0747, #2498). Populated at app-ready
|
|
316
|
+
#: alongside ``OWNER_RESOLVERS`` — empty before then, so ``allocate`` is a no-op
|
|
317
|
+
#: during app loading, exactly as for the project path.
|
|
318
|
+
PROGRAM_SEQUENCE_MODELS: set[type[models.Model]] = set()
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def register_sequence_receivers() -> None:
|
|
322
|
+
"""Populate the resolver map and connect the calendar-link receiver.
|
|
323
|
+
|
|
324
|
+
Called from ``SyncConfig.ready``.
|
|
325
|
+
"""
|
|
326
|
+
from django.db.models.signals import post_save
|
|
327
|
+
|
|
328
|
+
from trueppm_api.apps.access.models import ProgramMembership
|
|
329
|
+
from trueppm_api.apps.projects.models import Calendar, Program, Project
|
|
330
|
+
|
|
331
|
+
OWNER_RESOLVERS.clear()
|
|
332
|
+
OWNER_RESOLVERS.update(_resolvers())
|
|
333
|
+
|
|
334
|
+
# ADR-0747: the two program-scoped synced models. Deliberately NOT in
|
|
335
|
+
# OWNER_RESOLVERS — they resolve to no project, and adding them there would
|
|
336
|
+
# make them silently unallocated rather than program-allocated.
|
|
337
|
+
PROGRAM_SEQUENCE_MODELS.clear()
|
|
338
|
+
PROGRAM_SEQUENCE_MODELS.update({Program, ProgramMembership})
|
|
339
|
+
|
|
340
|
+
def _on_project_saved(sender: type, instance: Any, created: bool, **kwargs: Any) -> None:
|
|
341
|
+
"""Allocate the calendar when a project starts pointing at it.
|
|
342
|
+
|
|
343
|
+
A ``Calendar`` created before any project references it resolves to no
|
|
344
|
+
owner, so it keeps ``sync_seq = 0`` and would never be delivered. The
|
|
345
|
+
moment a project links to it — at creation, or when the FK is repointed —
|
|
346
|
+
it gains an owner and must be allocated, exactly once.
|
|
347
|
+
|
|
348
|
+
``raw`` saves (loaddata) bypass app logic; the DB may be mid-fixture-load,
|
|
349
|
+
so skip — the backfill migration or a later real save establishes it.
|
|
350
|
+
"""
|
|
351
|
+
if kwargs.get("raw") or instance.calendar_id is None:
|
|
352
|
+
return
|
|
353
|
+
previous = getattr(instance, "_sync_loaded_calendar_id", None)
|
|
354
|
+
if not created and previous == instance.calendar_id:
|
|
355
|
+
return
|
|
356
|
+
instance._sync_loaded_calendar_id = instance.calendar_id
|
|
357
|
+
calendar = Calendar.objects.filter(pk=instance.calendar_id).first()
|
|
358
|
+
if calendar is None:
|
|
359
|
+
return
|
|
360
|
+
allocate(calendar)
|
|
361
|
+
if calendar.sync_seq:
|
|
362
|
+
# A plain .update(): the calendar's own content did not change, so
|
|
363
|
+
# this must not bump server_version or write a history row.
|
|
364
|
+
Calendar.objects.filter(pk=calendar.pk).update(sync_seq=calendar.sync_seq)
|
|
365
|
+
|
|
366
|
+
post_save.connect(
|
|
367
|
+
_on_project_saved,
|
|
368
|
+
sender=Project,
|
|
369
|
+
dispatch_uid="sync_sequence_project_calendar_link",
|
|
370
|
+
weak=False,
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def allocate_board_event_seq(project_id: Any) -> int | None:
|
|
375
|
+
"""Draw the next WebSocket replay ``seq`` for ``project_id`` (ADR-1285, #4386).
|
|
376
|
+
|
|
377
|
+
**Must run inside the same ``transaction.atomic()`` as the ``BoardEvent``
|
|
378
|
+
INSERT that carries the value.** The ``UPDATE … RETURNING`` takes the
|
|
379
|
+
project's :class:`~trueppm_api.apps.sync.models.BoardEventSequence` row lock
|
|
380
|
+
and holds it until that transaction commits, so a second event on the project
|
|
381
|
+
waits for the first to commit before drawing — commit order equals allocation
|
|
382
|
+
order — and a rolled-back event takes its value back with it, so the committed
|
|
383
|
+
values stay dense.
|
|
384
|
+
|
|
385
|
+
The counter row is created lazily on a project's first replayable event. Two
|
|
386
|
+
concurrent first events are safe: ``get_or_create`` resolves the INSERT race
|
|
387
|
+
inside its own savepoint, and both then serialize on the row lock.
|
|
388
|
+
|
|
389
|
+
Returns ``None`` when the project does not exist (a broadcast that names a
|
|
390
|
+
deleted or non-project id): the caller then broadcasts without a ``seq``,
|
|
391
|
+
exactly as for an ephemeral event.
|
|
392
|
+
"""
|
|
393
|
+
from trueppm_api.apps.projects.models import Project
|
|
394
|
+
from trueppm_api.apps.sync.models import BoardEventSequence
|
|
395
|
+
from trueppm_api.core.db import increment_returning
|
|
396
|
+
|
|
397
|
+
seq = increment_returning(BoardEventSequence, "value", project_id)
|
|
398
|
+
if seq is not None:
|
|
399
|
+
return seq
|
|
400
|
+
# Checked before the INSERT rather than left to the FK: Django's FKs are
|
|
401
|
+
# DEFERRABLE INITIALLY DEFERRED, so a missing project would only fail at the
|
|
402
|
+
# outermost COMMIT — outside any except clause the caller can put around this.
|
|
403
|
+
if not Project.objects.filter(pk=project_id).exists():
|
|
404
|
+
return None
|
|
405
|
+
BoardEventSequence.objects.get_or_create(project_id=project_id)
|
|
406
|
+
return increment_returning(BoardEventSequence, "value", project_id)
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
#: Gap placed between the old global ``BoardEvent`` id space and every project's new
|
|
410
|
+
#: per-project ``seq`` space by the #4386 migration (ADR-1285 §6).
|
|
411
|
+
#:
|
|
412
|
+
#: Seeding a project at its highest old id is not enough on its own. During a
|
|
413
|
+
#: rolling update the old pods keep minting *global* ids from the same identity
|
|
414
|
+
#: sequence, just above that seed, while the new pods hand out per-project values
|
|
415
|
+
#: in the same range. A client fed by both could then hold a cursor equal to a
|
|
416
|
+
#: project's head while having skipped one of its seqs, and the replay would owe it
|
|
417
|
+
#: nothing. Shifting every seed up by 2**32 puts the two spaces about four billion
|
|
418
|
+
#: apart: no id an old pod can mint during a minutes-long window reaches a
|
|
419
|
+
#: project's seq space, and every pre-upgrade cursor sits below its project's seed.
|
|
420
|
+
#:
|
|
421
|
+
#: Range: the column is a signed 64-bit ``BIGINT`` (max 2**63 - 1), and the web
|
|
422
|
+
#: client compares seqs as JavaScript numbers, exact up to 2**53 - 1. A seed is
|
|
423
|
+
#: an existing id plus 2**32 (about 4.3e9), so both limits are many orders of
|
|
424
|
+
#: magnitude away. The allocator's ``value + 1`` and the ``(project, seq)``
|
|
425
|
+
#: uniqueness work on the shifted values exactly as on small ones.
|
|
426
|
+
BOARD_EVENT_SEQ_UPGRADE_OFFSET = 2**32
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def seed_board_event_sequences(
|
|
430
|
+
board_event_model: type[models.Model],
|
|
431
|
+
sequence_model: type[models.Model],
|
|
432
|
+
project_model: type[models.Model],
|
|
433
|
+
connection: Any,
|
|
434
|
+
) -> None:
|
|
435
|
+
"""Start every project's replay counter above any cursor an old client can hold.
|
|
436
|
+
|
|
437
|
+
Run once by the migration that introduced ``BoardEvent.seq`` (ADR-1285, #4386).
|
|
438
|
+
It takes the models as arguments so the migration can pass its historical ones,
|
|
439
|
+
and so the outcome is testable without importing a migration module.
|
|
440
|
+
|
|
441
|
+
Before #4386 a client's cursor was a *global* ``BoardEvent`` id. The existing
|
|
442
|
+
rows are deliberately **not** renumbered: they keep ``seq = NULL`` and stay
|
|
443
|
+
live-only, so the replay never serves them. Instead each project's counter is
|
|
444
|
+
seeded at the project's highest old global id plus
|
|
445
|
+
:data:`BOARD_EVENT_SEQ_UPGRADE_OFFSET`. Every global id, whether it was issued
|
|
446
|
+
before the upgrade or by an old pod during the rolling update, is then below its
|
|
447
|
+
project's seed. On reconnect, ``head - since`` exceeds the number of rows that
|
|
448
|
+
carry a ``seq``, so **every pre-upgrade cursor gets exactly one
|
|
449
|
+
``resync_required``** and continues from ``latest_seq``.
|
|
450
|
+
|
|
451
|
+
Renumbering the old rows per project, the first design, was wrong. A rank ``r``
|
|
452
|
+
is at most the old id ``c`` it replaced, so a cursor ``c`` that landed at or
|
|
453
|
+
below the new head replayed ``(c, head]`` and silently skipped ranks
|
|
454
|
+
``r + 1 .. c``.
|
|
455
|
+
|
|
456
|
+
A project with no surviving rows still needs a seed: a client may hold a cursor
|
|
457
|
+
from rows the purge has since removed. Any id ever assigned is at most the
|
|
458
|
+
table's identity sequence value, so those projects are seeded there, plus the
|
|
459
|
+
same offset. For their members that one constant reveals how many events the
|
|
460
|
+
installation had written by the upgrade. It is a single fixed number, not the
|
|
461
|
+
ongoing per-frame volume the old design leaked.
|
|
462
|
+
"""
|
|
463
|
+
events = board_event_model._meta.db_table
|
|
464
|
+
q_events = connection.ops.quote_name(events)
|
|
465
|
+
q_sequences = connection.ops.quote_name(sequence_model._meta.db_table)
|
|
466
|
+
q_projects = connection.ops.quote_name(project_model._meta.db_table)
|
|
467
|
+
with connection.cursor() as cursor:
|
|
468
|
+
cursor.execute("SELECT pg_get_serial_sequence(%s, 'id')", [events])
|
|
469
|
+
row = cursor.fetchone()
|
|
470
|
+
id_sequence = row[0] if row else None
|
|
471
|
+
global_floor = 0
|
|
472
|
+
if id_sequence:
|
|
473
|
+
# The name comes from the catalog, already schema-qualified and quoted.
|
|
474
|
+
# nosemgrep: formatted-sql-query, sqlalchemy-execute-raw-query
|
|
475
|
+
cursor.execute(
|
|
476
|
+
f"SELECT CASE WHEN is_called THEN last_value ELSE 0 END FROM {id_sequence}" # nosec B608
|
|
477
|
+
)
|
|
478
|
+
global_floor = int(cursor.fetchone()[0])
|
|
479
|
+
# nosemgrep: formatted-sql-query, sqlalchemy-execute-raw-query
|
|
480
|
+
cursor.execute(
|
|
481
|
+
f"INSERT INTO {q_sequences} (project_id, value) " # nosec B608 - names from _meta
|
|
482
|
+
f"SELECT p.id, COALESCE(m.max_id, %s) + %s FROM {q_projects} AS p "
|
|
483
|
+
f"LEFT JOIN (SELECT project_id, MAX(id) AS max_id FROM {q_events} "
|
|
484
|
+
f"GROUP BY project_id) AS m ON m.project_id = p.id "
|
|
485
|
+
f"ON CONFLICT (project_id) DO UPDATE "
|
|
486
|
+
f"SET value = GREATEST({q_sequences}.value, EXCLUDED.value)",
|
|
487
|
+
[global_floor, BOARD_EVENT_SEQ_UPGRADE_OFFSET],
|
|
488
|
+
)
|