trueppm-api 0.4.0b2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- trueppm_api/__init__.py +14 -0
- trueppm_api/apps/access/__init__.py +0 -0
- trueppm_api/apps/access/apps.py +19 -0
- trueppm_api/apps/access/groups.py +518 -0
- trueppm_api/apps/access/management/__init__.py +0 -0
- trueppm_api/apps/access/management/commands/__init__.py +0 -0
- trueppm_api/apps/access/management/commands/create_admin.py +164 -0
- trueppm_api/apps/access/management/commands/migrate_locked.py +96 -0
- trueppm_api/apps/access/management/commands/revoke_api_tokens.py +209 -0
- trueppm_api/apps/access/migrations/0001_initial.py +61 -0
- trueppm_api/apps/access/migrations/0001_squashed_0012_programmembership_role_title.py +167 -0
- trueppm_api/apps/access/migrations/0002_alter_projectmembership_id.py +29 -0
- trueppm_api/apps/access/migrations/0003_projectmembership_soft_delete.py +24 -0
- trueppm_api/apps/access/migrations/0004_alter_projectmembership_role.py +34 -0
- trueppm_api/apps/access/migrations/0005_program_entity_and_membership.py +76 -0
- trueppm_api/apps/access/migrations/0006_role_ordinal_spacing.py +113 -0
- trueppm_api/apps/access/migrations/0007_projectmembership_joined_at_and_more.py +35 -0
- trueppm_api/apps/access/migrations/0008_projectmembership_source_group.py +25 -0
- trueppm_api/apps/access/migrations/0009_projectmembership_pm_proj_serverver_idx.py +20 -0
- trueppm_api/apps/access/migrations/0010_programmembership_joined_at_and_more.py +38 -0
- trueppm_api/apps/access/migrations/0011_alter_programmembership_unique_together_and_more.py +36 -0
- trueppm_api/apps/access/migrations/0012_programmembership_role_title.py +17 -0
- trueppm_api/apps/access/migrations/0013_userdefinedmentiongroup.py +82 -0
- trueppm_api/apps/access/migrations/0014_programmembership_progm_serverver_idx.py +19 -0
- trueppm_api/apps/access/migrations/0015_programuserdefinedmentiongroup.py +86 -0
- trueppm_api/apps/access/migrations/0016_externalstakeholder.py +67 -0
- trueppm_api/apps/access/migrations/0017_viewer_ordinal_one.py +132 -0
- trueppm_api/apps/access/migrations/0018_programmembership_sync_seq_and_more.py +52 -0
- trueppm_api/apps/access/migrations/0019_programmembership_reinstated_at_and_more.py +22 -0
- trueppm_api/apps/access/migrations/__init__.py +0 -0
- trueppm_api/apps/access/models.py +588 -0
- trueppm_api/apps/access/permissions.py +2837 -0
- trueppm_api/apps/access/serializers.py +946 -0
- trueppm_api/apps/access/services.py +667 -0
- trueppm_api/apps/access/signals.py +103 -0
- trueppm_api/apps/access/tasks.py +50 -0
- trueppm_api/apps/access/throttles.py +95 -0
- trueppm_api/apps/access/urls.py +191 -0
- trueppm_api/apps/access/views.py +1931 -0
- trueppm_api/apps/agents/__init__.py +6 -0
- trueppm_api/apps/agents/apps.py +11 -0
- trueppm_api/apps/agents/canonical.py +73 -0
- trueppm_api/apps/agents/deferred.py +108 -0
- trueppm_api/apps/agents/management/__init__.py +0 -0
- trueppm_api/apps/agents/management/commands/__init__.py +0 -0
- trueppm_api/apps/agents/management/commands/audit_prune.py +137 -0
- trueppm_api/apps/agents/management/commands/audit_verify.py +101 -0
- trueppm_api/apps/agents/middleware.py +44 -0
- trueppm_api/apps/agents/migrations/0001_initial.py +231 -0
- trueppm_api/apps/agents/migrations/0002_agentactioncheckpoint.py +71 -0
- trueppm_api/apps/agents/migrations/0003_agentactionrefusaldetail.py +58 -0
- trueppm_api/apps/agents/migrations/__init__.py +0 -0
- trueppm_api/apps/agents/models.py +371 -0
- trueppm_api/apps/agents/refusal.py +103 -0
- trueppm_api/apps/agents/serializers.py +85 -0
- trueppm_api/apps/agents/services.py +397 -0
- trueppm_api/apps/agents/signals.py +30 -0
- trueppm_api/apps/agents/urls.py +15 -0
- trueppm_api/apps/agents/views.py +108 -0
- trueppm_api/apps/csvimport/__init__.py +12 -0
- trueppm_api/apps/csvimport/apps.py +11 -0
- trueppm_api/apps/csvimport/mapping.py +363 -0
- trueppm_api/apps/csvimport/migrations/0001_initial.py +74 -0
- trueppm_api/apps/csvimport/migrations/0002_schedule_outline_batch_operations.py +37 -0
- trueppm_api/apps/csvimport/migrations/0003_csvimportrequest_date_order_and_more.py +26 -0
- trueppm_api/apps/csvimport/migrations/__init__.py +0 -0
- trueppm_api/apps/csvimport/models.py +111 -0
- trueppm_api/apps/csvimport/parser.py +1825 -0
- trueppm_api/apps/csvimport/services.py +48 -0
- trueppm_api/apps/csvimport/tasks.py +418 -0
- trueppm_api/apps/csvimport/template.py +37 -0
- trueppm_api/apps/csvimport/urls.py +50 -0
- trueppm_api/apps/csvimport/views.py +793 -0
- trueppm_api/apps/history/__init__.py +0 -0
- trueppm_api/apps/history/apps.py +12 -0
- trueppm_api/apps/history/changelog.py +359 -0
- trueppm_api/apps/history/diff_policy.py +177 -0
- trueppm_api/apps/history/serializers.py +104 -0
- trueppm_api/apps/history/signals.py +82 -0
- trueppm_api/apps/history/tasks.py +105 -0
- trueppm_api/apps/history/urls.py +38 -0
- trueppm_api/apps/history/views.py +443 -0
- trueppm_api/apps/idempotency/__init__.py +0 -0
- trueppm_api/apps/idempotency/apps.py +11 -0
- trueppm_api/apps/idempotency/migrations/0001_initial.py +71 -0
- trueppm_api/apps/idempotency/migrations/__init__.py +0 -0
- trueppm_api/apps/idempotency/mixins.py +226 -0
- trueppm_api/apps/idempotency/models.py +75 -0
- trueppm_api/apps/idempotency/tasks.py +61 -0
- trueppm_api/apps/integrations/__init__.py +13 -0
- trueppm_api/apps/integrations/apps.py +59 -0
- trueppm_api/apps/integrations/connections.py +806 -0
- trueppm_api/apps/integrations/encryption.py +92 -0
- trueppm_api/apps/integrations/external_sources.py +837 -0
- trueppm_api/apps/integrations/git_automation_services.py +231 -0
- trueppm_api/apps/integrations/git_webhook_auth.py +243 -0
- trueppm_api/apps/integrations/http.py +403 -0
- trueppm_api/apps/integrations/me_work.py +218 -0
- trueppm_api/apps/integrations/migrations/0001_initial.py +64 -0
- trueppm_api/apps/integrations/migrations/0001_squashed_0006_tasklink_description_tasklink_preview_type_and_more.py +187 -0
- trueppm_api/apps/integrations/migrations/0002_tasklink.py +67 -0
- trueppm_api/apps/integrations/migrations/0003_tasklink_tasklink_serverver_idx.py +17 -0
- trueppm_api/apps/integrations/migrations/0004_tasklink_custom_title_tasklink_labels.py +25 -0
- trueppm_api/apps/integrations/migrations/0005_boardautomation.py +56 -0
- trueppm_api/apps/integrations/migrations/0006_tasklink_description_tasklink_preview_type_and_more.py +40 -0
- trueppm_api/apps/integrations/migrations/0007_tasklink_created_at.py +23 -0
- trueppm_api/apps/integrations/migrations/0008_integrationcredential_config_externalworkitem.py +66 -0
- trueppm_api/apps/integrations/migrations/0009_externalsyncrequest_and_more.py +96 -0
- trueppm_api/apps/integrations/migrations/0010_externalworkitem_due_date.py +17 -0
- trueppm_api/apps/integrations/migrations/0011_tasklink_sync_seq_tasklink_tasklink_syncseq_idx.py +22 -0
- trueppm_api/apps/integrations/migrations/0012_boardautomation_last_delivery_at_and_more.py +42 -0
- trueppm_api/apps/integrations/migrations/0013_externalsyncrequest_attempt_count_and_more.py +26 -0
- trueppm_api/apps/integrations/migrations/__init__.py +0 -0
- trueppm_api/apps/integrations/models.py +622 -0
- trueppm_api/apps/integrations/notification_channels.py +57 -0
- trueppm_api/apps/integrations/opengraph.py +229 -0
- trueppm_api/apps/integrations/outgoing.py +201 -0
- trueppm_api/apps/integrations/providers.py +519 -0
- trueppm_api/apps/integrations/registry.py +345 -0
- trueppm_api/apps/integrations/serializers.py +453 -0
- trueppm_api/apps/integrations/services.py +171 -0
- trueppm_api/apps/integrations/tasks.py +731 -0
- trueppm_api/apps/integrations/throttles.py +165 -0
- trueppm_api/apps/integrations/urls.py +77 -0
- trueppm_api/apps/integrations/views.py +1106 -0
- trueppm_api/apps/jiraimport/__init__.py +8 -0
- trueppm_api/apps/jiraimport/apps.py +11 -0
- trueppm_api/apps/jiraimport/migrations/0001_initial.py +72 -0
- trueppm_api/apps/jiraimport/migrations/__init__.py +0 -0
- trueppm_api/apps/jiraimport/models.py +70 -0
- trueppm_api/apps/jiraimport/parser.py +350 -0
- trueppm_api/apps/jiraimport/services.py +46 -0
- trueppm_api/apps/jiraimport/tasks.py +277 -0
- trueppm_api/apps/jiraimport/urls.py +15 -0
- trueppm_api/apps/jiraimport/views.py +153 -0
- trueppm_api/apps/msproject/__init__.py +0 -0
- trueppm_api/apps/msproject/apps.py +11 -0
- trueppm_api/apps/msproject/dataclasses.py +153 -0
- trueppm_api/apps/msproject/exporter.py +500 -0
- trueppm_api/apps/msproject/extended_attributes.py +77 -0
- trueppm_api/apps/msproject/importer.py +1095 -0
- trueppm_api/apps/msproject/migrations/0001_import_request.py +77 -0
- trueppm_api/apps/msproject/migrations/0001_squashed_0002_importrequest_creates_project.py +80 -0
- trueppm_api/apps/msproject/migrations/0002_importrequest_creates_project.py +15 -0
- trueppm_api/apps/msproject/migrations/__init__.py +0 -0
- trueppm_api/apps/msproject/models.py +81 -0
- trueppm_api/apps/msproject/parser.py +1231 -0
- trueppm_api/apps/msproject/serializers.py +136 -0
- trueppm_api/apps/msproject/services.py +58 -0
- trueppm_api/apps/msproject/tasks.py +436 -0
- trueppm_api/apps/msproject/urls.py +39 -0
- trueppm_api/apps/msproject/views.py +531 -0
- trueppm_api/apps/notifications/__init__.py +7 -0
- trueppm_api/apps/notifications/apps.py +15 -0
- trueppm_api/apps/notifications/backfill.py +79 -0
- trueppm_api/apps/notifications/categories.py +112 -0
- trueppm_api/apps/notifications/delivery_limits.py +258 -0
- trueppm_api/apps/notifications/digests.py +447 -0
- trueppm_api/apps/notifications/email_backend.py +330 -0
- trueppm_api/apps/notifications/email_health.py +128 -0
- trueppm_api/apps/notifications/migrations/0001_initial.py +207 -0
- trueppm_api/apps/notifications/migrations/0001_squashed_0006_notification_task.py +294 -0
- trueppm_api/apps/notifications/migrations/0002_project_notification_preference.py +67 -0
- trueppm_api/apps/notifications/migrations/0003_projectnotificationpreference_paused.py +17 -0
- trueppm_api/apps/notifications/migrations/0004_clean_unknown_matrix_keys.py +30 -0
- trueppm_api/apps/notifications/migrations/0005_notification_body_notification_event_type_and_more.py +27 -0
- trueppm_api/apps/notifications/migrations/0006_notification_task.py +25 -0
- trueppm_api/apps/notifications/migrations/0007_workspaceemailsettings.py +80 -0
- trueppm_api/apps/notifications/migrations/0008_notification_snoozed_until.py +17 -0
- trueppm_api/apps/notifications/migrations/0009_usernotificationsettings.py +43 -0
- trueppm_api/apps/notifications/migrations/0010_projectnotificationpreference_schema_version.py +20 -0
- trueppm_api/apps/notifications/migrations/0011_usernotificationsettings_digest_hour_and_more.py +96 -0
- trueppm_api/apps/notifications/migrations/__init__.py +0 -0
- trueppm_api/apps/notifications/models.py +1197 -0
- trueppm_api/apps/notifications/receivers.py +155 -0
- trueppm_api/apps/notifications/schema_migrations.py +56 -0
- trueppm_api/apps/notifications/serializers.py +961 -0
- trueppm_api/apps/notifications/services.py +1451 -0
- trueppm_api/apps/notifications/tasks.py +711 -0
- trueppm_api/apps/notifications/throttles.py +98 -0
- trueppm_api/apps/notifications/urls.py +97 -0
- trueppm_api/apps/notifications/views.py +904 -0
- trueppm_api/apps/observability/__init__.py +0 -0
- trueppm_api/apps/observability/apps.py +31 -0
- trueppm_api/apps/observability/logging.py +213 -0
- trueppm_api/apps/observability/migrations/0001_initial.py +35 -0
- trueppm_api/apps/observability/migrations/0001_squashed_0003_alter_purgerun_trigger.py +154 -0
- trueppm_api/apps/observability/migrations/0002_purgerun_retentionpolicy_retentionschedule.py +131 -0
- trueppm_api/apps/observability/migrations/0003_alter_purgerun_trigger.py +21 -0
- trueppm_api/apps/observability/migrations/0004_alter_retentionpolicy_key.py +28 -0
- trueppm_api/apps/observability/migrations/__init__.py +0 -0
- trueppm_api/apps/observability/models.py +141 -0
- trueppm_api/apps/observability/otel/__init__.py +45 -0
- trueppm_api/apps/observability/otel/attributes.py +247 -0
- trueppm_api/apps/observability/otel/export_health.py +533 -0
- trueppm_api/apps/observability/otel/instrumentation.py +351 -0
- trueppm_api/apps/observability/otel/metrics.py +551 -0
- trueppm_api/apps/observability/otel/provider.py +511 -0
- trueppm_api/apps/observability/otel/request_attributes.py +204 -0
- trueppm_api/apps/observability/purge_registry.py +124 -0
- trueppm_api/apps/observability/retention.py +139 -0
- trueppm_api/apps/observability/selectors.py +969 -0
- trueppm_api/apps/observability/serializers.py +142 -0
- trueppm_api/apps/observability/services.py +407 -0
- trueppm_api/apps/observability/tasks.py +266 -0
- trueppm_api/apps/observability/urls.py +27 -0
- trueppm_api/apps/observability/views.py +523 -0
- trueppm_api/apps/profiles/__init__.py +0 -0
- trueppm_api/apps/profiles/apps.py +15 -0
- trueppm_api/apps/profiles/constants.py +54 -0
- trueppm_api/apps/profiles/migrations/0001_initial.py +55 -0
- trueppm_api/apps/profiles/migrations/0001_squashed_0004_userprofile_role_context.py +125 -0
- trueppm_api/apps/profiles/migrations/0002_userprofile_hidden_views.py +17 -0
- trueppm_api/apps/profiles/migrations/0003_projectvisit.py +63 -0
- trueppm_api/apps/profiles/migrations/0004_userprofile_role_context.py +26 -0
- trueppm_api/apps/profiles/migrations/0005_userprofile_schedule_in_deliver.py +20 -0
- trueppm_api/apps/profiles/migrations/0006_userprofile_date_format_userprofile_timezone.py +36 -0
- trueppm_api/apps/profiles/migrations/0007_userpin.py +91 -0
- trueppm_api/apps/profiles/migrations/0008_remove_userprofile_schedule_in_deliver.py +71 -0
- trueppm_api/apps/profiles/migrations/0009_auth_user_email_upper_idx.py +81 -0
- trueppm_api/apps/profiles/migrations/__init__.py +0 -0
- trueppm_api/apps/profiles/models.py +276 -0
- trueppm_api/apps/profiles/serializers.py +199 -0
- trueppm_api/apps/profiles/services.py +466 -0
- trueppm_api/apps/profiles/views.py +124 -0
- trueppm_api/apps/projects/__init__.py +0 -0
- trueppm_api/apps/projects/actual_date_rules.py +163 -0
- trueppm_api/apps/projects/amend.py +133 -0
- trueppm_api/apps/projects/apps.py +38 -0
- trueppm_api/apps/projects/asset_feed.py +459 -0
- trueppm_api/apps/projects/asset_views.py +317 -0
- trueppm_api/apps/projects/attachment_policy.py +278 -0
- trueppm_api/apps/projects/authentication.py +449 -0
- trueppm_api/apps/projects/backfill.py +287 -0
- trueppm_api/apps/projects/backlog_services.py +324 -0
- trueppm_api/apps/projects/backlog_views.py +302 -0
- trueppm_api/apps/projects/batch_operation_views.py +264 -0
- trueppm_api/apps/projects/blocker_services.py +510 -0
- trueppm_api/apps/projects/board_activity.py +462 -0
- trueppm_api/apps/projects/board_activity_views.py +186 -0
- trueppm_api/apps/projects/board_lanes.py +134 -0
- trueppm_api/apps/projects/bulk_settings.py +194 -0
- trueppm_api/apps/projects/bundled_templates.py +215 -0
- trueppm_api/apps/projects/calendar_settings.py +180 -0
- trueppm_api/apps/projects/ceremony_views.py +244 -0
- trueppm_api/apps/projects/commit_moment.py +204 -0
- trueppm_api/apps/projects/config_notice.py +1325 -0
- trueppm_api/apps/projects/custom_field_values.py +266 -0
- trueppm_api/apps/projects/decisions_services.py +93 -0
- trueppm_api/apps/projects/decisions_views.py +94 -0
- trueppm_api/apps/projects/estimation_scale.py +126 -0
- trueppm_api/apps/projects/export_bundle.py +353 -0
- trueppm_api/apps/projects/fixtures/seeds/atlas-platform-launch.json +4256 -0
- trueppm_api/apps/projects/fixtures/seeds/aurora-mobile-app.json +2000 -0
- trueppm_api/apps/projects/fixtures/seeds/bayside-civic-center.json +1825 -0
- trueppm_api/apps/projects/fixtures/seeds/ga-launch.json +1905 -0
- trueppm_api/apps/projects/fixtures/seeds/helios-crm-replacement.json +1409 -0
- trueppm_api/apps/projects/guardrail_policy_source.py +269 -0
- trueppm_api/apps/projects/inbound_sync.py +555 -0
- trueppm_api/apps/projects/iteration_label.py +85 -0
- trueppm_api/apps/projects/lifecycle.py +191 -0
- trueppm_api/apps/projects/management/__init__.py +0 -0
- trueppm_api/apps/projects/management/commands/__init__.py +0 -0
- trueppm_api/apps/projects/management/commands/backfill_in_progress_status.py +92 -0
- trueppm_api/apps/projects/management/commands/create_demo_share_link.py +368 -0
- trueppm_api/apps/projects/management/commands/export_program.py +50 -0
- trueppm_api/apps/projects/management/commands/import_seed.py +165 -0
- trueppm_api/apps/projects/management/commands/load_sample_project.py +247 -0
- trueppm_api/apps/projects/management/commands/seed_capacity.py +405 -0
- trueppm_api/apps/projects/management/commands/seed_integration_fixtures.py +178 -0
- trueppm_api/apps/projects/management/commands/shift_sample_dates.py +120 -0
- trueppm_api/apps/projects/mcp_settings.py +305 -0
- trueppm_api/apps/projects/methodology.py +178 -0
- trueppm_api/apps/projects/migrations/0001_initial.py +208 -0
- trueppm_api/apps/projects/migrations/0001_squashed_0094_project_decisions_policy.py +5595 -0
- trueppm_api/apps/projects/migrations/0002_alter_uuid_pk_serialize_false.py +57 -0
- trueppm_api/apps/projects/migrations/0003_rename_task_project_index.py +25 -0
- trueppm_api/apps/projects/migrations/0004_soft_delete_and_dependency_versioned.py +70 -0
- trueppm_api/apps/projects/migrations/0005_task_assignee.py +33 -0
- trueppm_api/apps/projects/migrations/0006_task_utilization_index.py +31 -0
- trueppm_api/apps/projects/migrations/0007_task_is_milestone.py +28 -0
- trueppm_api/apps/projects/migrations/0008_baseline.py +119 -0
- trueppm_api/apps/projects/migrations/0009_historicaldependency_historicalproject_and_more.py +210 -0
- trueppm_api/apps/projects/migrations/0010_risk.py +229 -0
- trueppm_api/apps/projects/migrations/0011_task_status.py +27 -0
- trueppm_api/apps/projects/migrations/0012_task_status.py +27 -0
- trueppm_api/apps/projects/migrations/0013_task_planned_start.py +24 -0
- trueppm_api/apps/projects/migrations/0014_short_hex_ids.py +74 -0
- trueppm_api/apps/projects/migrations/0015_backfill_short_ids.py +48 -0
- trueppm_api/apps/projects/migrations/0016_task_actual_dates.py +42 -0
- trueppm_api/apps/projects/migrations/0017_boardcolumnconfig.py +43 -0
- trueppm_api/apps/projects/migrations/0018_estimation_governance.py +62 -0
- trueppm_api/apps/projects/migrations/0019_backfill_wbs_paths.py +28 -0
- trueppm_api/apps/projects/migrations/0020_task_status_5col.py +65 -0
- trueppm_api/apps/projects/migrations/0021_boardsavedview.py +64 -0
- trueppm_api/apps/projects/migrations/0022_task_status_changed_at_priority_rank.py +35 -0
- trueppm_api/apps/projects/migrations/0023_risk_framework_fields.py +96 -0
- trueppm_api/apps/projects/migrations/0024_riskcomment.py +52 -0
- trueppm_api/apps/projects/migrations/0025_sprint.py +352 -0
- trueppm_api/apps/projects/migrations/0026_project_methodology.py +30 -0
- trueppm_api/apps/projects/migrations/0027_sprint_retro.py +89 -0
- trueppm_api/apps/projects/migrations/0028_normalize_notes_field.py +42 -0
- trueppm_api/apps/projects/migrations/0029_complete_implies_full_progress.py +38 -0
- trueppm_api/apps/projects/migrations/0030_review_implies_full_progress.py +37 -0
- trueppm_api/apps/projects/migrations/0031_task_remaining_points.py +22 -0
- trueppm_api/apps/projects/migrations/0032_task_is_subtask_and_sprint_scope_change.py +90 -0
- trueppm_api/apps/projects/migrations/0033_task_my_work_index.py +38 -0
- trueppm_api/apps/projects/migrations/0034_inbound_task_sync.py +313 -0
- trueppm_api/apps/projects/migrations/0035_retro_versioned_and_suggested_assignee.py +173 -0
- trueppm_api/apps/projects/migrations/0036_program_entity_and_membership.py +157 -0
- trueppm_api/apps/projects/migrations/0037_sprint_capacity_points.py +20 -0
- trueppm_api/apps/projects/migrations/0038_taskcomment_commentreaction_commentacknowledgement_and_more.py +233 -0
- trueppm_api/apps/projects/migrations/0039_apitoken_and_more.py +88 -0
- trueppm_api/apps/projects/migrations/0040_program_general_fields.py +119 -0
- trueppm_api/apps/projects/migrations/0041_ceremony_template_and_phase_gate_config.py +210 -0
- trueppm_api/apps/projects/migrations/0041_project_general_fields.py +106 -0
- trueppm_api/apps/projects/migrations/0042_merge_20260522_0410.py +12 -0
- trueppm_api/apps/projects/migrations/0042_program_rollup_config.py +95 -0
- trueppm_api/apps/projects/migrations/0042_workflow_color_and_custom_fields.py +94 -0
- trueppm_api/apps/projects/migrations/0043_merge_20260522_1118.py +10 -0
- trueppm_api/apps/projects/migrations/0044_merge_20260522_rollup.py +10 -0
- trueppm_api/apps/projects/migrations/0044_project_archive_program_close.py +119 -0
- trueppm_api/apps/projects/migrations/0045_program_risk_policy.py +78 -0
- trueppm_api/apps/projects/migrations/0046_merge_20260523_2248.py +12 -0
- trueppm_api/apps/projects/migrations/0047_apitokenauditentry_program_and_more.py +55 -0
- trueppm_api/apps/projects/migrations/0048_historicalprogram_color_program_color.py +32 -0
- trueppm_api/apps/projects/migrations/0049_backlog_item.py +112 -0
- trueppm_api/apps/projects/migrations/0050_backlog_item_trgm_search.py +26 -0
- trueppm_api/apps/projects/migrations/0051_alter_calendar_working_days_alter_dependency_lag_and_more.py +144 -0
- trueppm_api/apps/projects/migrations/0052_recurring_tasks.py +278 -0
- trueppm_api/apps/projects/migrations/0053_dependency_dep_pred_serverver_idx_and_more.py +56 -0
- trueppm_api/apps/projects/migrations/0054_sprintscopechange_goal_impact_and_more.py +117 -0
- trueppm_api/apps/projects/migrations/0055_historicaltask_sprint_pending_and_more.py +51 -0
- trueppm_api/apps/projects/migrations/0056_historicalproject_prioritization_model_and_more.py +276 -0
- trueppm_api/apps/projects/migrations/0057_historicalsprint_binding_committed_snapshot_and_more.py +58 -0
- trueppm_api/apps/projects/migrations/0058_historicaltask_delivery_mode_and_more.py +72 -0
- trueppm_api/apps/projects/migrations/0059_forecastsnapshot.py +80 -0
- trueppm_api/apps/projects/migrations/0060_historicalprojectsignalprivacypolicy_and_more.py +90 -0
- trueppm_api/apps/projects/migrations/0061_baselinetask_story_points.py +17 -0
- trueppm_api/apps/projects/migrations/0062_project_is_sample.py +22 -0
- trueppm_api/apps/projects/migrations/0063_historicalsprint_wip_limit_sprint_wip_limit.py +22 -0
- trueppm_api/apps/projects/migrations/0064_sprinttaskoutcome.py +97 -0
- trueppm_api/apps/projects/migrations/0065_historicalsprint_goal_outcome_sprint_goal_outcome.py +32 -0
- trueppm_api/apps/projects/migrations/0066_historicalproject_lead_project_lead.py +38 -0
- trueppm_api/apps/projects/migrations/0067_sprint_exclude_from_velocity.py +22 -0
- trueppm_api/apps/projects/migrations/0068_project_recalculated_at.py +23 -0
- trueppm_api/apps/projects/migrations/0069_iteration_label.py +22 -0
- trueppm_api/apps/projects/migrations/0070_historicalprogram_iteration_label_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0071_backfill_iteration_label_inherit.py +35 -0
- trueppm_api/apps/projects/migrations/0072_pulseresponse_retroboarditem.py +129 -0
- trueppm_api/apps/projects/migrations/0073_sprinttaskoutcome_demo_ready.py +17 -0
- trueppm_api/apps/projects/migrations/0074_historicaltask_blocked_reason_task_blocked_reason.py +30 -0
- trueppm_api/apps/projects/migrations/0075_risk_decimal_short_id.py +42 -0
- trueppm_api/apps/projects/migrations/0076_sprinttaskoutcome_review_polish.py +45 -0
- trueppm_api/apps/projects/migrations/0077_task_blocker_fields.py +111 -0
- trueppm_api/apps/projects/migrations/0078_alter_risktask_unique_together_and_more.py +22 -0
- trueppm_api/apps/projects/migrations/0079_historicalproject_status_date_project_status_date.py +22 -0
- trueppm_api/apps/projects/migrations/0080_alter_historicaltask_type_alter_task_type.py +46 -0
- trueppm_api/apps/projects/migrations/0081_historicalprogram_allow_guests_and_more.py +52 -0
- trueppm_api/apps/projects/migrations/0082_project_last_sync_version.py +90 -0
- trueppm_api/apps/projects/migrations/0083_historicalprogram_mc_history_attribution_audience_and_more.py +108 -0
- trueppm_api/apps/projects/migrations/0084_tasknote.py +71 -0
- trueppm_api/apps/projects/migrations/0085_sprint_ix_sprint_velocity.py +33 -0
- trueppm_api/apps/projects/migrations/0086_board_card_search_trgm.py +33 -0
- trueppm_api/apps/projects/migrations/0087_historicalprogram_task_duration_change_percent_policy_and_more.py +155 -0
- trueppm_api/apps/projects/migrations/0088_historicalprogram_allowed_attachment_types_and_more.py +61 -0
- trueppm_api/apps/projects/migrations/0089_signalceilingraiseproposal_signalceilingraisevote_and_more.py +157 -0
- trueppm_api/apps/projects/migrations/0090_historicaltask_proj_histdate_index.py +44 -0
- trueppm_api/apps/projects/migrations/0091_project_board_cadence.py +39 -0
- trueppm_api/apps/projects/migrations/0092_program_target_date.py +22 -0
- trueppm_api/apps/projects/migrations/0093_cross_project_dependency_consent.py +58 -0
- trueppm_api/apps/projects/migrations/0094_project_decisions_policy.py +90 -0
- trueppm_api/apps/projects/migrations/0095_poker_session_vote.py +127 -0
- trueppm_api/apps/projects/migrations/0095_squashed_0097_release_0_3.py +225 -0
- trueppm_api/apps/projects/migrations/0096_crossprojectslipconflict.py +91 -0
- trueppm_api/apps/projects/migrations/0097_backlogitem_backlogitem_tags_gin_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0098_historicalproject_show_baselines_and_more.py +52 -0
- trueppm_api/apps/projects/migrations/0099_apitoken_scopes.py +48 -0
- trueppm_api/apps/projects/migrations/0100_task_dep_serverver_idx_concurrent.py +61 -0
- trueppm_api/apps/projects/migrations/0101_dependency_deleted_at_task_deleted_at.py +22 -0
- trueppm_api/apps/projects/migrations/0102_project_deleted_at_project_proj_isdel_deletedat_idx.py +25 -0
- trueppm_api/apps/projects/migrations/0103_taskdurationchangeevent_task_dur_evt_sprint_idx.py +18 -0
- trueppm_api/apps/projects/migrations/0104_historicalproject_stale_task_threshold_days_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0104_trash_restore_1113.py +38 -0
- trueppm_api/apps/projects/migrations/0105_historical_changelog_indexes.py +62 -0
- trueppm_api/apps/projects/migrations/0106_boardsavedview_schema_version.py +20 -0
- trueppm_api/apps/projects/migrations/0107_task_activity_event.py +70 -0
- trueppm_api/apps/projects/migrations/0108_remove_apitoken_api_token_scope_xor_and_more.py +117 -0
- trueppm_api/apps/projects/migrations/0109_projectexportjob.py +82 -0
- trueppm_api/apps/projects/migrations/0110_sharelink.py +123 -0
- trueppm_api/apps/projects/migrations/0111_projectcalendarlayer.py +67 -0
- trueppm_api/apps/projects/migrations/0112_sharelink_expires_at_alter_sharelink_content_kind.py +31 -0
- trueppm_api/apps/projects/migrations/0113_alter_historicaltask_percent_complete_and_more.py +35 -0
- trueppm_api/apps/projects/migrations/0114_historicalproject_default_member_role_and_more.py +28 -0
- trueppm_api/apps/projects/migrations/0115_historicalproject_end_date_shift_threshold_days_and_more.py +22 -0
- trueppm_api/apps/projects/migrations/0116_alter_taskactivityevent_event_type.py +28 -0
- trueppm_api/apps/projects/migrations/0117_label_tasklabel_label_tasks_and_more.py +126 -0
- trueppm_api/apps/projects/migrations/0118_programexportjob.py +82 -0
- trueppm_api/apps/projects/migrations/0119_historicalprogram_calendar_program_calendar.py +36 -0
- trueppm_api/apps/projects/migrations/0120_alter_backlogitem_item_type.py +29 -0
- trueppm_api/apps/projects/migrations/0121_task_three_point_estimate_ordered.py +64 -0
- trueppm_api/apps/projects/migrations/0122_task_relation.py +173 -0
- trueppm_api/apps/projects/migrations/0123_remove_historicalproject_agile_features_and_more.py +20 -0
- trueppm_api/apps/projects/migrations/0124_dependency_is_driving.py +17 -0
- trueppm_api/apps/projects/migrations/0125_historicalprogram_estimation_scale_and_more.py +68 -0
- trueppm_api/apps/projects/migrations/0126_projectcustomfield_show_on_card_taskcustomfieldvalue.py +80 -0
- trueppm_api/apps/projects/migrations/0127_historicalprogram_mcp_enabled_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0128_acceptancecriterion_sync_seq_apitoken_sync_seq_and_more.py +192 -0
- trueppm_api/apps/projects/migrations/0129_sharelink_show_milestone_dates.py +20 -0
- trueppm_api/apps/projects/migrations/0130_programimportjob.py +85 -0
- trueppm_api/apps/projects/migrations/0131_historicalprogram_sprint_picker_ready_only_default_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0132_task_scheduled_start.py +17 -0
- trueppm_api/apps/projects/migrations/0133_project_status_date_floor_armed_at.py +17 -0
- trueppm_api/apps/projects/migrations/0134_task_edited_at_task_seeded_at_task_source_id_and_more.py +65 -0
- trueppm_api/apps/projects/migrations/0135_projecttemplate_templateapplication_and_more.py +175 -0
- trueppm_api/apps/projects/migrations/0136_schedule_outline_batch_operations.py +118 -0
- trueppm_api/apps/projects/migrations/0137_collapse_dual_scope_api_tokens.py +54 -0
- trueppm_api/apps/projects/migrations/0138_apitokenauditentry_api_token_audit_owner_idx.py +18 -0
- trueppm_api/apps/projects/migrations/0139_historicaltask_duration_unit_task_duration_unit.py +32 -0
- trueppm_api/apps/projects/migrations/0140_historicaltask_auto_container_and_more.py +113 -0
- trueppm_api/apps/projects/migrations/0141_historicalproject_draft_started_at_and_more.py +44 -0
- trueppm_api/apps/projects/migrations/0142_baseline_calendar_hours_per_day_and_more.py +31 -0
- trueppm_api/apps/projects/migrations/0143_sprintcloserequest_failure_reason_and_more.py +42 -0
- trueppm_api/apps/projects/migrations/0144_projecttemplate_source_project_and_more.py +35 -0
- trueppm_api/apps/projects/migrations/0145_seed_bundled_templates.py +65 -0
- trueppm_api/apps/projects/migrations/0146_historicaltask_board_lane_task_board_lane.py +32 -0
- trueppm_api/apps/projects/migrations/0147_structuraloperation.py +128 -0
- trueppm_api/apps/projects/migrations/0148_task_unique_task_wbs_path_per_project_live.py +76 -0
- trueppm_api/apps/projects/migrations/0149_drop_workshops_tables.py +42 -0
- trueppm_api/apps/projects/migrations/0150_clear_uncommitted_cpm_output.py +130 -0
- trueppm_api/apps/projects/migrations/0151_config_notice_request.py +70 -0
- trueppm_api/apps/projects/migrations/0152_aggregate_task_wbs_name_idx.py +20 -0
- trueppm_api/apps/projects/migrations/0153_program_sample_anchor_date.py +22 -0
- trueppm_api/apps/projects/migrations/__init__.py +0 -0
- trueppm_api/apps/projects/models.py +8946 -0
- trueppm_api/apps/projects/poker_services.py +154 -0
- trueppm_api/apps/projects/poker_views.py +398 -0
- trueppm_api/apps/projects/product_backlog_services.py +450 -0
- trueppm_api/apps/projects/program_label_services.py +94 -0
- trueppm_api/apps/projects/program_label_views.py +189 -0
- trueppm_api/apps/projects/program_rollup.py +575 -0
- trueppm_api/apps/projects/program_schedule.py +745 -0
- trueppm_api/apps/projects/program_views.py +3220 -0
- trueppm_api/apps/projects/project_templates.py +422 -0
- trueppm_api/apps/projects/receivers.py +234 -0
- trueppm_api/apps/projects/refusal_codes.py +140 -0
- trueppm_api/apps/projects/reorder_services.py +145 -0
- trueppm_api/apps/projects/restructure_hooks.py +54 -0
- trueppm_api/apps/projects/retro_board_services.py +368 -0
- trueppm_api/apps/projects/retro_services.py +285 -0
- trueppm_api/apps/projects/risk_import.py +396 -0
- trueppm_api/apps/projects/row_identity.py +113 -0
- trueppm_api/apps/projects/schema_migrations.py +228 -0
- trueppm_api/apps/projects/schemas/seed_v1.json +493 -0
- trueppm_api/apps/projects/schemas/seed_v2.json +892 -0
- trueppm_api/apps/projects/seed/__init__.py +61 -0
- trueppm_api/apps/projects/seed/exporter.py +1644 -0
- trueppm_api/apps/projects/seed/forecast_backfill.py +247 -0
- trueppm_api/apps/projects/seed/importer.py +2390 -0
- trueppm_api/apps/projects/seed/reanchor.py +551 -0
- trueppm_api/apps/projects/seed/reldates.py +126 -0
- trueppm_api/apps/projects/seed/replace.py +126 -0
- trueppm_api/apps/projects/seed/replay.py +1691 -0
- trueppm_api/apps/projects/seed/replay_ctx.py +41 -0
- trueppm_api/apps/projects/seed/samples.py +427 -0
- trueppm_api/apps/projects/seed/validation.py +1149 -0
- trueppm_api/apps/projects/serializers.py +12310 -0
- trueppm_api/apps/projects/services.py +6966 -0
- trueppm_api/apps/projects/share_serializers.py +132 -0
- trueppm_api/apps/projects/share_services.py +325 -0
- trueppm_api/apps/projects/share_views.py +374 -0
- trueppm_api/apps/projects/sharing_settings.py +187 -0
- trueppm_api/apps/projects/signal_privacy_services.py +909 -0
- trueppm_api/apps/projects/signal_privacy_views.py +476 -0
- trueppm_api/apps/projects/signals.py +132 -0
- trueppm_api/apps/projects/slip_conflict.py +266 -0
- trueppm_api/apps/projects/sprint_cadence.py +453 -0
- trueppm_api/apps/projects/sprint_picker_settings.py +132 -0
- trueppm_api/apps/projects/standup.py +304 -0
- trueppm_api/apps/projects/standup_views.py +99 -0
- trueppm_api/apps/projects/structural_operation_services.py +727 -0
- trueppm_api/apps/projects/structural_operation_views.py +311 -0
- trueppm_api/apps/projects/surface_visibility.py +126 -0
- trueppm_api/apps/projects/task_batch_services.py +379 -0
- trueppm_api/apps/projects/task_bulk.py +1152 -0
- trueppm_api/apps/projects/task_classification.py +561 -0
- trueppm_api/apps/projects/task_classification_defaults.py +95 -0
- trueppm_api/apps/projects/task_duration_settings.py +142 -0
- trueppm_api/apps/projects/task_grouping.py +754 -0
- trueppm_api/apps/projects/tasks.py +1917 -0
- trueppm_api/apps/projects/template_divergence.py +168 -0
- trueppm_api/apps/projects/template_services.py +160 -0
- trueppm_api/apps/projects/template_tasks.py +258 -0
- trueppm_api/apps/projects/template_views.py +799 -0
- trueppm_api/apps/projects/throttles.py +311 -0
- trueppm_api/apps/projects/urls.py +1011 -0
- trueppm_api/apps/projects/utilization.py +885 -0
- trueppm_api/apps/projects/views.py +20572 -0
- trueppm_api/apps/projects/wbs_paths.py +119 -0
- trueppm_api/apps/resources/__init__.py +0 -0
- trueppm_api/apps/resources/apps.py +8 -0
- trueppm_api/apps/resources/capacity.py +121 -0
- trueppm_api/apps/resources/migrations/0001_initial.py +68 -0
- trueppm_api/apps/resources/migrations/0001_squashed_0013_alter_projectresource_unique_together_and_more.py +281 -0
- trueppm_api/apps/resources/migrations/0002_alter_uuid_pk_serialize_false.py +36 -0
- trueppm_api/apps/resources/migrations/0003_resource_soft_delete.py +30 -0
- trueppm_api/apps/resources/migrations/0004_add_taskresource_resource_index.py +25 -0
- trueppm_api/apps/resources/migrations/0005_resource_job_role.py +20 -0
- trueppm_api/apps/resources/migrations/0006_skill.py +34 -0
- trueppm_api/apps/resources/migrations/0007_resource_skill.py +63 -0
- trueppm_api/apps/resources/migrations/0008_project_resource.py +63 -0
- trueppm_api/apps/resources/migrations/0009_task_skill_requirement.py +60 -0
- trueppm_api/apps/resources/migrations/0010_alter_resourceskill_options_and_more.py +20 -0
- trueppm_api/apps/resources/migrations/0011_alter_projectresource_notes.py +17 -0
- trueppm_api/apps/resources/migrations/0012_add_resource_user_fk.py +26 -0
- trueppm_api/apps/resources/migrations/0013_alter_projectresource_unique_together_and_more.py +53 -0
- trueppm_api/apps/resources/migrations/0014_projectresource_sync_seq_resource_sync_seq_and_more.py +37 -0
- trueppm_api/apps/resources/migrations/0015_projectresource_deactivated_with_resource.py +17 -0
- trueppm_api/apps/resources/migrations/__init__.py +0 -0
- trueppm_api/apps/resources/models.py +298 -0
- trueppm_api/apps/resources/serializers.py +426 -0
- trueppm_api/apps/resources/services.py +465 -0
- trueppm_api/apps/resources/urls.py +28 -0
- trueppm_api/apps/resources/views.py +1749 -0
- trueppm_api/apps/scheduling/__init__.py +0 -0
- trueppm_api/apps/scheduling/apps.py +153 -0
- trueppm_api/apps/scheduling/calendars.py +119 -0
- trueppm_api/apps/scheduling/deadletter.py +91 -0
- trueppm_api/apps/scheduling/forecast_history_settings.py +215 -0
- trueppm_api/apps/scheduling/forecast_staleness.py +313 -0
- trueppm_api/apps/scheduling/graph_guard.py +263 -0
- trueppm_api/apps/scheduling/management/__init__.py +0 -0
- trueppm_api/apps/scheduling/management/commands/__init__.py +0 -0
- trueppm_api/apps/scheduling/management/commands/prune_forecast_snapshots.py +53 -0
- trueppm_api/apps/scheduling/migrations/0001_failed_task.py +62 -0
- trueppm_api/apps/scheduling/migrations/0001_squashed_0007_projectforecastsnapshot.py +309 -0
- trueppm_api/apps/scheduling/migrations/0002_schedulerequest.py +78 -0
- trueppm_api/apps/scheduling/migrations/0003_add_schedule_request_reason.py +27 -0
- trueppm_api/apps/scheduling/migrations/0004_velocity_suggestion.py +94 -0
- trueppm_api/apps/scheduling/migrations/0005_montecarlorun.py +61 -0
- trueppm_api/apps/scheduling/migrations/0006_montecarlorun_distribution.py +17 -0
- trueppm_api/apps/scheduling/migrations/0007_projectforecastsnapshot.py +65 -0
- trueppm_api/apps/scheduling/migrations/0008_alter_schedulerequest_reason.py +28 -0
- trueppm_api/apps/scheduling/migrations/0008_folded_schedulerequest_reason.py +34 -0
- trueppm_api/apps/scheduling/migrations/0009_alter_schedulerequest_reason.py +29 -0
- trueppm_api/apps/scheduling/migrations/0010_failedtask_resolution_note_failedtask_resolved_at_and_more.py +43 -0
- trueppm_api/apps/scheduling/migrations/0011_failedtask_project.py +26 -0
- trueppm_api/apps/scheduling/migrations/0012_montecarlorun_diagnostic.py +17 -0
- trueppm_api/apps/scheduling/migrations/0013_montecarlorun_status_date.py +17 -0
- trueppm_api/apps/scheduling/migrations/0014_montecarlorun_plan_version.py +17 -0
- trueppm_api/apps/scheduling/migrations/__init__.py +0 -0
- trueppm_api/apps/scheduling/models.py +516 -0
- trueppm_api/apps/scheduling/receivers.py +160 -0
- trueppm_api/apps/scheduling/risk_premium.py +283 -0
- trueppm_api/apps/scheduling/serializers.py +564 -0
- trueppm_api/apps/scheduling/services.py +1182 -0
- trueppm_api/apps/scheduling/signals.py +56 -0
- trueppm_api/apps/scheduling/tasks.py +1984 -0
- trueppm_api/apps/scheduling/telemetry.py +80 -0
- trueppm_api/apps/scheduling/units.py +35 -0
- trueppm_api/apps/scheduling/urls.py +57 -0
- trueppm_api/apps/scheduling/views.py +2564 -0
- trueppm_api/apps/sso/__init__.py +0 -0
- trueppm_api/apps/sso/apps.py +19 -0
- trueppm_api/apps/sso/extensions.py +130 -0
- trueppm_api/apps/sso/management/__init__.py +0 -0
- trueppm_api/apps/sso/management/commands/__init__.py +0 -0
- trueppm_api/apps/sso/management/commands/seed_sso_keycloak.py +172 -0
- trueppm_api/apps/sso/migrations/0001_initial.py +119 -0
- trueppm_api/apps/sso/migrations/0002_remove_oidcprovider_workspace_ssoproviderpolicy_and_more.py +169 -0
- trueppm_api/apps/sso/migrations/__init__.py +0 -0
- trueppm_api/apps/sso/models.py +148 -0
- trueppm_api/apps/sso/serializers.py +437 -0
- trueppm_api/apps/sso/services.py +1385 -0
- trueppm_api/apps/sso/urls.py +47 -0
- trueppm_api/apps/sso/views.py +935 -0
- trueppm_api/apps/sync/__init__.py +0 -0
- trueppm_api/apps/sync/apps.py +15 -0
- trueppm_api/apps/sync/backfill.py +208 -0
- trueppm_api/apps/sync/broadcast.py +320 -0
- trueppm_api/apps/sync/conflict.py +262 -0
- trueppm_api/apps/sync/consumers.py +471 -0
- trueppm_api/apps/sync/migrations/0001_initial.py +59 -0
- trueppm_api/apps/sync/migrations/0001_squashed_0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +77 -0
- trueppm_api/apps/sync/migrations/0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +41 -0
- trueppm_api/apps/sync/migrations/0003_boardevent.py +46 -0
- trueppm_api/apps/sync/migrations/0004_seed_sync_seq.py +24 -0
- trueppm_api/apps/sync/migrations/0005_program_sync_sequence.py +35 -0
- trueppm_api/apps/sync/migrations/__init__.py +0 -0
- trueppm_api/apps/sync/models.py +225 -0
- trueppm_api/apps/sync/pagination.py +231 -0
- trueppm_api/apps/sync/sequence.py +371 -0
- trueppm_api/apps/sync/serializers.py +687 -0
- trueppm_api/apps/sync/tasks.py +260 -0
- trueppm_api/apps/sync/throttles.py +175 -0
- trueppm_api/apps/sync/upload.py +501 -0
- trueppm_api/apps/sync/urls.py +17 -0
- trueppm_api/apps/sync/views.py +930 -0
- trueppm_api/apps/sync/ws_auth.py +193 -0
- trueppm_api/apps/taskruns/__init__.py +0 -0
- trueppm_api/apps/taskruns/apps.py +11 -0
- trueppm_api/apps/taskruns/migrations/0001_initial.py +92 -0
- trueppm_api/apps/taskruns/migrations/__init__.py +0 -0
- trueppm_api/apps/taskruns/models.py +68 -0
- trueppm_api/apps/taskruns/serializers.py +92 -0
- trueppm_api/apps/taskruns/tasks.py +60 -0
- trueppm_api/apps/taskruns/tracker.py +286 -0
- trueppm_api/apps/taskruns/urls.py +46 -0
- trueppm_api/apps/taskruns/views.py +195 -0
- trueppm_api/apps/teams/__init__.py +8 -0
- trueppm_api/apps/teams/apps.py +16 -0
- trueppm_api/apps/teams/migrations/0001_initial.py +139 -0
- trueppm_api/apps/teams/migrations/0001_squashed_0002_default_teams.py +142 -0
- trueppm_api/apps/teams/migrations/0002_default_teams.py +62 -0
- trueppm_api/apps/teams/migrations/0003_team_sync_seq_teammembership_sync_seq.py +22 -0
- trueppm_api/apps/teams/migrations/__init__.py +0 -0
- trueppm_api/apps/teams/models.py +145 -0
- trueppm_api/apps/teams/permissions.py +149 -0
- trueppm_api/apps/teams/serializers.py +95 -0
- trueppm_api/apps/teams/services.py +434 -0
- trueppm_api/apps/teams/signals.py +61 -0
- trueppm_api/apps/teams/urls.py +34 -0
- trueppm_api/apps/teams/views.py +201 -0
- trueppm_api/apps/timetracking/__init__.py +0 -0
- trueppm_api/apps/timetracking/apps.py +9 -0
- trueppm_api/apps/timetracking/migrations/0001_initial.py +117 -0
- trueppm_api/apps/timetracking/migrations/0002_timesheetsubmission.py +46 -0
- trueppm_api/apps/timetracking/migrations/0003_timeentry_deleted_at_timeentry_deleted_by.py +31 -0
- trueppm_api/apps/timetracking/migrations/0004_timeentry_sync_seq_and_more.py +24 -0
- trueppm_api/apps/timetracking/migrations/__init__.py +0 -0
- trueppm_api/apps/timetracking/models.py +178 -0
- trueppm_api/apps/timetracking/serializers.py +175 -0
- trueppm_api/apps/timetracking/services.py +117 -0
- trueppm_api/apps/timetracking/urls.py +47 -0
- trueppm_api/apps/timetracking/views.py +526 -0
- trueppm_api/apps/webhooks/__init__.py +0 -0
- trueppm_api/apps/webhooks/apps.py +8 -0
- trueppm_api/apps/webhooks/backfill.py +90 -0
- trueppm_api/apps/webhooks/dispatch.py +144 -0
- trueppm_api/apps/webhooks/migrations/0001_initial.py +146 -0
- trueppm_api/apps/webhooks/migrations/0001_squashed_0007_sprint_lifecycle_events.py +243 -0
- trueppm_api/apps/webhooks/migrations/0002_alter_webhook_events.py +32 -0
- trueppm_api/apps/webhooks/migrations/0003_delivery_status_created_idx.py +17 -0
- trueppm_api/apps/webhooks/migrations/0004_webhook_program_alter_webhook_project_and_more.py +64 -0
- trueppm_api/apps/webhooks/migrations/0005_webhook_delivery_sequence_numbers.py +30 -0
- trueppm_api/apps/webhooks/migrations/0006_webhook_format_alter_webhook_events_and_more.py +61 -0
- trueppm_api/apps/webhooks/migrations/0007_sprint_lifecycle_events.py +63 -0
- trueppm_api/apps/webhooks/migrations/0008_risk_baseline_comment_events.py +73 -0
- trueppm_api/apps/webhooks/migrations/0009_encrypt_webhook_secret_and_failure_counters.py +90 -0
- trueppm_api/apps/webhooks/migrations/__init__.py +0 -0
- trueppm_api/apps/webhooks/models.py +372 -0
- trueppm_api/apps/webhooks/serializers.py +318 -0
- trueppm_api/apps/webhooks/tasks.py +663 -0
- trueppm_api/apps/webhooks/urls.py +22 -0
- trueppm_api/apps/webhooks/views.py +383 -0
- trueppm_api/apps/workflow_engine/__init__.py +0 -0
- trueppm_api/apps/workflow_engine/apps.py +23 -0
- trueppm_api/apps/workflow_engine/migrations/0001_initial.py +230 -0
- trueppm_api/apps/workflow_engine/migrations/0001_squashed_0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +234 -0
- trueppm_api/apps/workflow_engine/migrations/0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +16 -0
- trueppm_api/apps/workflow_engine/migrations/0003_workflowoutboxrow_workflow_outbox_recovery_idx.py +21 -0
- trueppm_api/apps/workflow_engine/migrations/__init__.py +0 -0
- trueppm_api/apps/workflow_engine/models.py +265 -0
- trueppm_api/apps/workflow_engine/tasks.py +368 -0
- trueppm_api/apps/workspace/__init__.py +0 -0
- trueppm_api/apps/workspace/apps.py +10 -0
- trueppm_api/apps/workspace/backfill.py +111 -0
- trueppm_api/apps/workspace/export.py +242 -0
- trueppm_api/apps/workspace/migrations/0001_initial.py +326 -0
- trueppm_api/apps/workspace/migrations/0001_squashed_0014_workspacemembership_availability_effective_from_and_more.py +829 -0
- trueppm_api/apps/workspace/migrations/0002_remove_workspace_fiscal_year_start_and_more.py +85 -0
- trueppm_api/apps/workspace/migrations/0003_workspaceexportjob.py +73 -0
- trueppm_api/apps/workspace/migrations/0004_alter_workspaceinvite_status.py +27 -0
- trueppm_api/apps/workspace/migrations/0005_workspace_iteration_label_and_more.py +26 -0
- trueppm_api/apps/workspace/migrations/0006_alter_groupmembership_unique_together_and_more.py +45 -0
- trueppm_api/apps/workspace/migrations/0007_workspace_public_sharing_override_policy.py +21 -0
- trueppm_api/apps/workspace/migrations/0008_workspace_mc_history_attribution_audience_and_more.py +44 -0
- trueppm_api/apps/workspace/migrations/0009_workspace_methodology_and_more.py +186 -0
- trueppm_api/apps/workspace/migrations/0010_workspace_logo.py +38 -0
- trueppm_api/apps/workspace/migrations/0011_historicalworkspace_task_duration_change_percent_override_policy_and_more.py +56 -0
- trueppm_api/apps/workspace/migrations/0012_historicalworkspace_allowed_attachment_types_and_more.py +64 -0
- trueppm_api/apps/workspace/migrations/0013_auditevent.py +70 -0
- trueppm_api/apps/workspace/migrations/0014_workspacemembership_availability_effective_from_and_more.py +39 -0
- trueppm_api/apps/workspace/migrations/0015_trash_restore_1113.py +34 -0
- trueppm_api/apps/workspace/migrations/0016_historicalworkspace_calendar_and_more.py +55 -0
- trueppm_api/apps/workspace/migrations/0017_historicalworkspace_estimation_scale_and_more.py +38 -0
- trueppm_api/apps/workspace/migrations/0018_historicalworkspace_feedback_enabled_and_more.py +32 -0
- trueppm_api/apps/workspace/migrations/0019_historicalworkspace_mcp_enabled_and_more.py +22 -0
- trueppm_api/apps/workspace/migrations/0020_viewer_ordinal_one.py +34 -0
- trueppm_api/apps/workspace/migrations/0021_group_sync_seq_groupmembership_sync_seq_and_more.py +27 -0
- trueppm_api/apps/workspace/migrations/0022_historicalworkspace_sprint_picker_ready_only_default_and_more.py +22 -0
- trueppm_api/apps/workspace/migrations/0023_alter_auditevent_event_type.py +32 -0
- trueppm_api/apps/workspace/migrations/0024_alter_auditevent_event_type.py +33 -0
- trueppm_api/apps/workspace/migrations/0025_alter_auditevent_event_type.py +36 -0
- trueppm_api/apps/workspace/migrations/0026_alter_auditevent_event_type.py +37 -0
- trueppm_api/apps/workspace/migrations/0027_alter_auditevent_event_type.py +42 -0
- trueppm_api/apps/workspace/migrations/__init__.py +0 -0
- trueppm_api/apps/workspace/models.py +857 -0
- trueppm_api/apps/workspace/permissions.py +187 -0
- trueppm_api/apps/workspace/serializers.py +590 -0
- trueppm_api/apps/workspace/services.py +919 -0
- trueppm_api/apps/workspace/signals.py +37 -0
- trueppm_api/apps/workspace/tasks.py +555 -0
- trueppm_api/apps/workspace/urls.py +113 -0
- trueppm_api/apps/workspace/views.py +1502 -0
- trueppm_api/asgi.py +42 -0
- trueppm_api/celery.py +31 -0
- trueppm_api/core/__init__.py +0 -0
- trueppm_api/core/admin_site.py +206 -0
- trueppm_api/core/auth_views.py +916 -0
- trueppm_api/core/constant_time.py +51 -0
- trueppm_api/core/csp.py +54 -0
- trueppm_api/core/db.py +87 -0
- trueppm_api/core/db_session.py +37 -0
- trueppm_api/core/exception_handlers.py +157 -0
- trueppm_api/core/export_downloads.py +56 -0
- trueppm_api/core/extension_providers.py +56 -0
- trueppm_api/core/extension_signals.py +147 -0
- trueppm_api/core/idempotent.py +260 -0
- trueppm_api/core/metadata.py +42 -0
- trueppm_api/core/middleware.py +51 -0
- trueppm_api/core/openapi.py +949 -0
- trueppm_api/core/password_reset.py +500 -0
- trueppm_api/core/protect_conflict.py +100 -0
- trueppm_api/core/ratelimit.py +148 -0
- trueppm_api/core/redis_throttle.py +82 -0
- trueppm_api/core/refresh_cookie_policy.py +156 -0
- trueppm_api/core/request_body.py +86 -0
- trueppm_api/core/security_checks.py +884 -0
- trueppm_api/core/storage_config.py +105 -0
- trueppm_api/core/text_decode.py +168 -0
- trueppm_api/core/throttling.py +344 -0
- trueppm_api/core/valkey.py +214 -0
- trueppm_api/core/valkey_cache.py +66 -0
- trueppm_api/core/valkey_checks.py +104 -0
- trueppm_api/core/valkey_config.py +39 -0
- trueppm_api/core/worker_broker_wait.py +149 -0
- trueppm_api/core/worker_heartbeat.py +134 -0
- trueppm_api/fields.py +36 -0
- trueppm_api/permissions.py +30 -0
- trueppm_api/py.typed +0 -0
- trueppm_api/routing.py +11 -0
- trueppm_api/settings/__init__.py +0 -0
- trueppm_api/settings/base.py +2455 -0
- trueppm_api/settings/dev.py +273 -0
- trueppm_api/settings/prod.py +228 -0
- trueppm_api/urls.py +153 -0
- trueppm_api/workflows/__init__.py +38 -0
- trueppm_api/workflows/backends/__init__.py +6 -0
- trueppm_api/workflows/backends/default.py +297 -0
- trueppm_api/workflows/consumers/__init__.py +11 -0
- trueppm_api/workflows/consumers/requeue_failed_task.py +65 -0
- trueppm_api/workflows/interface.py +148 -0
- trueppm_api/workflows/registry.py +106 -0
- trueppm_api/workflows/services.py +90 -0
- trueppm_api/wsgi.py +11 -0
- trueppm_api-0.4.0b2.dist-info/METADATA +70 -0
- trueppm_api-0.4.0b2.dist-info/RECORD +759 -0
- trueppm_api-0.4.0b2.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""Management command to bootstrap a TruePPM admin (superuser) account.
|
|
2
|
+
|
|
3
|
+
Runs automatically on container startup (both Docker Compose and Kubernetes).
|
|
4
|
+
Idempotent: if a superuser already exists the command exits immediately — it
|
|
5
|
+
will NOT reset a production password on re-deploy.
|
|
6
|
+
|
|
7
|
+
"A superuser already exists" is the *only* thing that stops it. When none does,
|
|
8
|
+
the command resolves ``DJANGO_SUPERUSER_EMAIL`` with ``get_or_create`` and then
|
|
9
|
+
sets ``is_staff``/``is_superuser`` and a fresh password unconditionally — so an
|
|
10
|
+
existing **non-superuser** holding that address is promoted and has its password
|
|
11
|
+
reset. That is intended (it is how an operator recovers an install whose only
|
|
12
|
+
superuser was demoted), but it is a privilege grant, so it is announced on stdout
|
|
13
|
+
rather than reported as an ordinary create (#3175).
|
|
14
|
+
|
|
15
|
+
Credential delivery
|
|
16
|
+
-------------------
|
|
17
|
+
The password is written to a file (default ``/tmp/trueppm_admin_password``)
|
|
18
|
+
with atomic 0o600 permissions so it never appears in container log aggregators
|
|
19
|
+
(CloudWatch, Datadog, etc.). Override the path via the
|
|
20
|
+
``TRUEPPM_ADMIN_PASSWORD_FILE`` environment variable. If the file write fails
|
|
21
|
+
the password falls back to management-command stdout *only* — it is never
|
|
22
|
+
passed to ``logger.*`` which would forward it to log aggregators.
|
|
23
|
+
|
|
24
|
+
Environment variables
|
|
25
|
+
---------------------
|
|
26
|
+
``DJANGO_SUPERUSER_EMAIL``
|
|
27
|
+
Admin email address (default: ``admin@example.com``).
|
|
28
|
+
|
|
29
|
+
The default is an RFC 2606 reserved domain, chosen so it can never be
|
|
30
|
+
delivered to. It used to be ``admin@trueppm.com`` — a domain the *vendor*
|
|
31
|
+
owns and the operator does not, which put every un-overridden install's
|
|
32
|
+
root account at an address its owner could not receive password-reset mail
|
|
33
|
+
at, and someone else could (#3175). Always set this explicitly.
|
|
34
|
+
``DJANGO_SUPERUSER_USERNAME``
|
|
35
|
+
Admin username; defaults to the local part of the email.
|
|
36
|
+
``DJANGO_SUPERUSER_PASSWORD``
|
|
37
|
+
Explicit password. When omitted a secure random password is generated
|
|
38
|
+
via :func:`secrets.token_urlsafe`.
|
|
39
|
+
``TRUEPPM_ADMIN_PASSWORD_FILE``
|
|
40
|
+
Path where the one-time password is written (default:
|
|
41
|
+
``/tmp/trueppm_admin_password``). Override in production to a
|
|
42
|
+
non-world-writable location (e.g. an emptyDir volume mount).
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
from __future__ import annotations
|
|
46
|
+
|
|
47
|
+
import logging
|
|
48
|
+
import os
|
|
49
|
+
import secrets
|
|
50
|
+
|
|
51
|
+
from django.contrib.auth import get_user_model
|
|
52
|
+
from django.core.management.base import BaseCommand
|
|
53
|
+
|
|
54
|
+
logger = logging.getLogger(__name__)
|
|
55
|
+
|
|
56
|
+
# Default intentionally uses /tmp for dev convenience; production deployments
|
|
57
|
+
# must set TRUEPPM_ADMIN_PASSWORD_FILE to a non-world-writable path.
|
|
58
|
+
_PASSWORD_FILE = os.environ.get( # nosec B108
|
|
59
|
+
"TRUEPPM_ADMIN_PASSWORD_FILE", "/tmp/trueppm_admin_password"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class Command(BaseCommand):
|
|
64
|
+
"""Bootstrap a TruePPM superuser on first run; no-op if one already exists."""
|
|
65
|
+
|
|
66
|
+
help = (
|
|
67
|
+
"Bootstrap a superuser on first run. "
|
|
68
|
+
"Writes the one-time password to a file rather than stdout "
|
|
69
|
+
"so it is not visible in container log aggregators."
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def handle(self, *args: object, **options: object) -> None:
|
|
73
|
+
"""Execute the command.
|
|
74
|
+
|
|
75
|
+
Exits immediately when a superuser already exists so that re-deploying
|
|
76
|
+
a Kubernetes pod or restarting the compose stack does not overwrite a
|
|
77
|
+
production password.
|
|
78
|
+
"""
|
|
79
|
+
User = get_user_model()
|
|
80
|
+
|
|
81
|
+
if User.objects.filter(is_superuser=True).exists():
|
|
82
|
+
self.stdout.write("Admin user already exists — skipping bootstrap.")
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
email = os.environ.get("DJANGO_SUPERUSER_EMAIL", "admin@example.com").strip()
|
|
86
|
+
username = os.environ.get("DJANGO_SUPERUSER_USERNAME", "").strip() or email.split("@")[0]
|
|
87
|
+
password = os.environ.get("DJANGO_SUPERUSER_PASSWORD", "").strip() or secrets.token_urlsafe(
|
|
88
|
+
16
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# get-or-create-ok: bootstrap command, single CLI/env admin email — no duplicate reachable
|
|
92
|
+
user, created = User.objects.get_or_create(
|
|
93
|
+
email=email,
|
|
94
|
+
defaults={"username": username},
|
|
95
|
+
)
|
|
96
|
+
# Bootstrap-only: password is an operator-supplied env var or a
|
|
97
|
+
# cryptographically random token (above), not interactive signup input —
|
|
98
|
+
# AUTH_PASSWORD_VALIDATORS govern the registration path, not superuser
|
|
99
|
+
# provisioning.
|
|
100
|
+
# nosemgrep: unvalidated-password
|
|
101
|
+
user.set_password(password)
|
|
102
|
+
user.username = username
|
|
103
|
+
user.is_staff = True
|
|
104
|
+
user.is_superuser = True
|
|
105
|
+
user.save()
|
|
106
|
+
|
|
107
|
+
# An existing row being promoted is a privilege grant on an account the
|
|
108
|
+
# operator did not necessarily know was there — say so plainly rather than
|
|
109
|
+
# letting it read like a routine bootstrap line.
|
|
110
|
+
if created:
|
|
111
|
+
action = "Created"
|
|
112
|
+
else:
|
|
113
|
+
action = "Promoted existing user to"
|
|
114
|
+
self.stdout.write(
|
|
115
|
+
self.style.WARNING(
|
|
116
|
+
f"WARNING: {email} already existed as a non-superuser. It has been "
|
|
117
|
+
"granted superuser + staff and its password has been RESET. If this "
|
|
118
|
+
"was not intended, set DJANGO_SUPERUSER_EMAIL to an unused address."
|
|
119
|
+
)
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Write to file rather than stdout — credentials in stdout end up in
|
|
123
|
+
# every log aggregator that ships container output.
|
|
124
|
+
pw_to_stdout = False
|
|
125
|
+
try:
|
|
126
|
+
flags = os.O_WRONLY | os.O_CREAT | os.O_TRUNC
|
|
127
|
+
# O_NOFOLLOW prevents a symlink attack on the world-writable /tmp
|
|
128
|
+
# directory (Linux and macOS only; ignored on platforms that lack it).
|
|
129
|
+
if hasattr(os, "O_NOFOLLOW"):
|
|
130
|
+
flags |= os.O_NOFOLLOW
|
|
131
|
+
# Mode 0o600 is applied atomically at file creation — avoids the
|
|
132
|
+
# TOCTOU window that a two-step open()+chmod() would leave.
|
|
133
|
+
fd = os.open(_PASSWORD_FILE, flags, 0o600)
|
|
134
|
+
with os.fdopen(fd, "w") as fh:
|
|
135
|
+
fh.write(password + "\n")
|
|
136
|
+
password_info = f"[REDACTED — written to {_PASSWORD_FILE}]"
|
|
137
|
+
except OSError as exc:
|
|
138
|
+
# File write failed. Fall back to management-command stdout only;
|
|
139
|
+
# do NOT use logger.* (log aggregators capture WARNING+ lines).
|
|
140
|
+
# No secret logged: the only arg is the OSError; the password is
|
|
141
|
+
# surfaced solely on command stdout below, never via the logger.
|
|
142
|
+
# nosemgrep: python-logger-credential-disclosure
|
|
143
|
+
logger.warning(
|
|
144
|
+
"create_admin: could not write password file (%s). "
|
|
145
|
+
"Credential printed to command stdout only.",
|
|
146
|
+
exc,
|
|
147
|
+
)
|
|
148
|
+
pw_to_stdout = True
|
|
149
|
+
password_info = password
|
|
150
|
+
|
|
151
|
+
self.stdout.write("")
|
|
152
|
+
self.stdout.write(self.style.WARNING("=" * 60))
|
|
153
|
+
self.stdout.write(self.style.WARNING(" TRUEPPM INITIAL ADMIN CREDENTIALS"))
|
|
154
|
+
self.stdout.write(self.style.WARNING("=" * 60))
|
|
155
|
+
self.stdout.write(f" {action} admin: {email} (username={username})")
|
|
156
|
+
self.stdout.write(f" Password: {password_info}")
|
|
157
|
+
if not pw_to_stdout:
|
|
158
|
+
self.stdout.write(
|
|
159
|
+
self.style.WARNING(
|
|
160
|
+
f" Retrieve the password from {_PASSWORD_FILE}, then delete the file."
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
self.stdout.write(self.style.WARNING("=" * 60))
|
|
164
|
+
self.stdout.write("")
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""``migrate`` serialized behind a PostgreSQL advisory lock (#3188).
|
|
2
|
+
|
|
3
|
+
The chart runs ``migrate`` as a **per-pod init container**, not as a
|
|
4
|
+
``pre-upgrade`` hook Job. At ``replicaCount: 1`` that is harmless; at 2 or more
|
|
5
|
+
every pod runs it concurrently against one database, and Django has no
|
|
6
|
+
concurrency control of its own — each process reads ``django_migrations``,
|
|
7
|
+
decides the same migration is unapplied, and both run it. The outcomes are a
|
|
8
|
+
duplicate-key error on the ``django_migrations`` insert, a lock timeout between
|
|
9
|
+
two concurrent ``ALTER TABLE``s, or a partially-applied DDL sequence, depending
|
|
10
|
+
on timing. All three surface as a crash-looping init container during a rollout,
|
|
11
|
+
which is the worst moment to be debugging schema state.
|
|
12
|
+
|
|
13
|
+
A session-level advisory lock is the cheap fix and needs no new infrastructure:
|
|
14
|
+
PostgreSQL already arbitrates it, the losers block rather than fail, and by the
|
|
15
|
+
time they acquire the lock the winner has finished so their own ``migrate`` is a
|
|
16
|
+
no-op. Advisory locks are also released automatically if the holder's connection
|
|
17
|
+
dies, so a killed init container cannot wedge the next rollout.
|
|
18
|
+
|
|
19
|
+
Deliberately a separate command rather than an override of ``migrate``: an
|
|
20
|
+
operator running ``manage.py migrate`` by hand should get Django's command with
|
|
21
|
+
Django's semantics, and a wrapper that silently changed it would be a surprise
|
|
22
|
+
in exactly the situation where surprises are expensive.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import time
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
from django.core.management import call_command
|
|
31
|
+
from django.core.management.base import BaseCommand, CommandError
|
|
32
|
+
from django.db import connection
|
|
33
|
+
|
|
34
|
+
#: Arbitrary but FIXED 64-bit key. Every TruePPM process must use the same one or
|
|
35
|
+
#: the lock arbitrates nothing; it is namespaced by the database, so two
|
|
36
|
+
#: TruePPM installs on separate databases never contend. Derived by hand rather
|
|
37
|
+
#: than hashed from a string so it is stable across Python versions —
|
|
38
|
+
#: ``hash()`` is salted per process and would silently give each pod its own lock.
|
|
39
|
+
MIGRATION_ADVISORY_LOCK_KEY = 4_113_188_001
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Command(BaseCommand):
|
|
43
|
+
help = "Run `migrate` while holding a PostgreSQL advisory lock (#3188)."
|
|
44
|
+
|
|
45
|
+
def add_arguments(self, parser: Any) -> None:
|
|
46
|
+
parser.add_argument(
|
|
47
|
+
"--lock-timeout",
|
|
48
|
+
type=int,
|
|
49
|
+
default=600,
|
|
50
|
+
help=(
|
|
51
|
+
"Seconds to wait for the lock before giving up. Must exceed the "
|
|
52
|
+
"longest migration run, since every other pod waits out the "
|
|
53
|
+
"winner. 0 waits forever."
|
|
54
|
+
),
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def handle(self, *args: Any, **options: Any) -> None:
|
|
58
|
+
if connection.vendor != "postgresql":
|
|
59
|
+
# SQLite has no advisory locks, and a non-PostgreSQL backend is
|
|
60
|
+
# necessarily a single-process dev setup where the race cannot occur.
|
|
61
|
+
self.stdout.write("Not PostgreSQL — running migrate without a lock.")
|
|
62
|
+
call_command("migrate", "--noinput")
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
timeout = int(options["lock_timeout"])
|
|
66
|
+
deadline = time.monotonic() + timeout if timeout else None
|
|
67
|
+
|
|
68
|
+
with connection.cursor() as cursor:
|
|
69
|
+
acquired = False
|
|
70
|
+
while not acquired:
|
|
71
|
+
# try_advisory_lock, not advisory_lock: blocking inside the
|
|
72
|
+
# database would hold the connection with no way to report
|
|
73
|
+
# progress or honor --lock-timeout. Polling costs one cheap
|
|
74
|
+
# round trip a second and keeps the timeout ours to enforce.
|
|
75
|
+
cursor.execute("SELECT pg_try_advisory_lock(%s)", [MIGRATION_ADVISORY_LOCK_KEY])
|
|
76
|
+
row = cursor.fetchone()
|
|
77
|
+
acquired = bool(row and row[0])
|
|
78
|
+
if acquired:
|
|
79
|
+
break
|
|
80
|
+
if deadline is not None and time.monotonic() >= deadline:
|
|
81
|
+
raise CommandError(
|
|
82
|
+
f"Timed out after {timeout}s waiting for the migration "
|
|
83
|
+
"advisory lock. Another pod is still migrating, or a "
|
|
84
|
+
"previous run's connection is still open."
|
|
85
|
+
)
|
|
86
|
+
self.stdout.write("Waiting for the migration advisory lock…")
|
|
87
|
+
time.sleep(1)
|
|
88
|
+
|
|
89
|
+
self.stdout.write(self.style.SUCCESS("Acquired the migration advisory lock."))
|
|
90
|
+
try:
|
|
91
|
+
call_command("migrate", "--noinput")
|
|
92
|
+
finally:
|
|
93
|
+
# Explicit unlock so a long-lived connection (the init container
|
|
94
|
+
# exits right after, but tests and manual runs do not) does not
|
|
95
|
+
# hold it until disconnect.
|
|
96
|
+
cursor.execute("SELECT pg_advisory_unlock(%s)", [MIGRATION_ADVISORY_LOCK_KEY])
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""``manage.py revoke_api_tokens`` — the operator's API-token breach-recovery lever (#2878).
|
|
2
|
+
|
|
3
|
+
**Why this exists.** ``administration/security.md`` told operators that rotating the JWT
|
|
4
|
+
signing key after a suspected token leak or an admin off-boarding signs everyone out. It
|
|
5
|
+
does — for sessions and JWTs. An API token is resolved by a SHA-256 hash lookup
|
|
6
|
+
(``apps/projects/authentication.py``), carries no signature, and is completely untouched
|
|
7
|
+
by key rotation: after following the documented procedure to the letter, every leaked
|
|
8
|
+
token on the instance is still live, and nothing in the runbook said so. This command is
|
|
9
|
+
the missing half of that procedure.
|
|
10
|
+
|
|
11
|
+
**Scope is a required choice, not a default.** Exactly one of:
|
|
12
|
+
|
|
13
|
+
* ``--user <username-or-email>`` — every *personal* token owned by one account. The
|
|
14
|
+
off-boarding / "this person's laptop was stolen" case.
|
|
15
|
+
* ``--all-personal`` — every personal token on the instance. The "our secret store
|
|
16
|
+
leaked" case. Leaves project- and program-scoped integration tokens alone, so team
|
|
17
|
+
CI keeps working.
|
|
18
|
+
* ``--all`` — every token of every kind, including integration tokens, **plus**
|
|
19
|
+
every active share link and configured git-automation webhook secret instance-
|
|
20
|
+
wide (#2939) — the other two durable grants a leaked token or compromised
|
|
21
|
+
session can mint that no other lever reaches. This *will* break every inbound
|
|
22
|
+
sync and public share link until an admin re-mints; it is the full-compromise
|
|
23
|
+
lever.
|
|
24
|
+
|
|
25
|
+
**Dry-run by default.** The command reports what it would revoke and exits; ``--commit``
|
|
26
|
+
performs the revocation, prompting for confirmation unless ``--yes`` is given. Revocation
|
|
27
|
+
is one-way — nothing in the codebase clears ``revoked_at`` — so a mistyped scope cannot
|
|
28
|
+
be undone, which is exactly why it does not act without being told twice.
|
|
29
|
+
|
|
30
|
+
Every revoked token gets an ``ApiTokenAuditEntry`` row with
|
|
31
|
+
``detail["reason"] = "operator_bulk_revoke"``, so the incident timeline can distinguish
|
|
32
|
+
an operator's containment sweep from a user's routine rotation. ``actor`` is null: a
|
|
33
|
+
command run from a shell has no Django user, and inventing one would be worse than
|
|
34
|
+
recording the absence.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
from typing import Any
|
|
40
|
+
|
|
41
|
+
from django.contrib.auth import get_user_model
|
|
42
|
+
from django.core.management.base import BaseCommand, CommandError
|
|
43
|
+
from django.db import transaction
|
|
44
|
+
from django.db.models import Q
|
|
45
|
+
from django.utils import timezone
|
|
46
|
+
|
|
47
|
+
REASON = "operator_bulk_revoke"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class Command(BaseCommand):
|
|
51
|
+
help = "Revoke API tokens instance-wide or for one account (breach recovery, #2878)."
|
|
52
|
+
|
|
53
|
+
def add_arguments(self, parser: Any) -> None:
|
|
54
|
+
scope = parser.add_mutually_exclusive_group(required=True)
|
|
55
|
+
scope.add_argument(
|
|
56
|
+
"--user",
|
|
57
|
+
type=str,
|
|
58
|
+
help="Revoke every personal access token owned by this username or email.",
|
|
59
|
+
)
|
|
60
|
+
scope.add_argument(
|
|
61
|
+
"--all-personal",
|
|
62
|
+
action="store_true",
|
|
63
|
+
help="Revoke every personal access token on the instance. Leaves project- "
|
|
64
|
+
"and program-scoped integration tokens alone.",
|
|
65
|
+
)
|
|
66
|
+
scope.add_argument(
|
|
67
|
+
"--all",
|
|
68
|
+
action="store_true",
|
|
69
|
+
help="Revoke EVERY API token, including project- and program-scoped "
|
|
70
|
+
"integration tokens. Breaks all inbound sync until re-minted.",
|
|
71
|
+
)
|
|
72
|
+
parser.add_argument(
|
|
73
|
+
"--commit",
|
|
74
|
+
action="store_true",
|
|
75
|
+
help="Actually revoke. Without this the command only reports.",
|
|
76
|
+
)
|
|
77
|
+
parser.add_argument(
|
|
78
|
+
"--yes",
|
|
79
|
+
action="store_true",
|
|
80
|
+
help="Skip the interactive confirmation (for use in a runbook script).",
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
def handle(self, *args: Any, **options: Any) -> None:
|
|
84
|
+
from trueppm_api.apps.projects.models import (
|
|
85
|
+
ApiToken,
|
|
86
|
+
ApiTokenAuditAction,
|
|
87
|
+
ApiTokenAuditEntry,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
target_user = None
|
|
91
|
+
if options["user"]:
|
|
92
|
+
User = get_user_model()
|
|
93
|
+
identifier = str(options["user"]).strip()
|
|
94
|
+
# Accept either handle so a runbook does not have to know which the operator
|
|
95
|
+
# has to hand during an incident.
|
|
96
|
+
#
|
|
97
|
+
# FAIL CLOSED on ambiguity. Django's stock ``auth.User`` puts **no**
|
|
98
|
+
# uniqueness constraint on ``email`` at all, and ``username`` uniqueness is
|
|
99
|
+
# case-*sensitive*, so ``__iexact`` can match several rows two ways. A
|
|
100
|
+
# ``.first()`` here would silently pick the lowest pk — during an incident,
|
|
101
|
+
# while reporting success — so the leaked token stays live and an uninvolved
|
|
102
|
+
# account's automations break instead. ``apps/sso/services.py`` and
|
|
103
|
+
# ``core/password_reset.py`` already treat this lookup as hazardous; this is
|
|
104
|
+
# the third caller and it must not be the one that guesses.
|
|
105
|
+
candidates = list(
|
|
106
|
+
User.objects.filter(
|
|
107
|
+
Q(username__iexact=identifier) | Q(email__iexact=identifier)
|
|
108
|
+
).order_by("pk")[:3]
|
|
109
|
+
)
|
|
110
|
+
if not candidates:
|
|
111
|
+
raise CommandError(f"No account matches {identifier!r} by username or email.")
|
|
112
|
+
if len(candidates) > 1:
|
|
113
|
+
listed = ", ".join(f"pk={u.pk} username={u.username!r}" for u in candidates)
|
|
114
|
+
raise CommandError(
|
|
115
|
+
f"{identifier!r} matches more than one account ({listed}). Re-run with "
|
|
116
|
+
"an unambiguous username so the sweep cannot contain the wrong "
|
|
117
|
+
"account."
|
|
118
|
+
)
|
|
119
|
+
target_user = candidates[0]
|
|
120
|
+
|
|
121
|
+
active = ApiToken.objects.filter(is_deleted=False, revoked_at__isnull=True)
|
|
122
|
+
if target_user is not None:
|
|
123
|
+
queryset = active.filter(owner=target_user)
|
|
124
|
+
scope_label = f"personal tokens owned by {target_user.username}"
|
|
125
|
+
elif options["all_personal"]:
|
|
126
|
+
queryset = active.filter(owner__isnull=False)
|
|
127
|
+
scope_label = "all personal access tokens on this instance"
|
|
128
|
+
else:
|
|
129
|
+
queryset = active
|
|
130
|
+
scope_label = "ALL API tokens on this instance (including integration tokens)"
|
|
131
|
+
|
|
132
|
+
doomed = list(queryset.only("pk", "token_prefix", "name", "owner", "project", "program"))
|
|
133
|
+
self.stdout.write(f"Scope: {scope_label}")
|
|
134
|
+
self.stdout.write(f"Active tokens matched: {len(doomed)}")
|
|
135
|
+
for token in doomed:
|
|
136
|
+
kind = "personal" if token.owner_id else ("project" if token.project_id else "program")
|
|
137
|
+
self.stdout.write(f" {token.token_prefix}… ({kind}) {token.name}")
|
|
138
|
+
|
|
139
|
+
# --all is the full-compromise lever: also sweep the two non-token durable
|
|
140
|
+
# grants a leaked token (or a compromised session) can mint (#2939). Counted
|
|
141
|
+
# ahead of --commit so a dry run reports the whole blast radius, not just
|
|
142
|
+
# the token count.
|
|
143
|
+
from trueppm_api.apps.integrations.models import BoardAutomation
|
|
144
|
+
from trueppm_api.apps.projects.models import ShareLink
|
|
145
|
+
|
|
146
|
+
links_matched = secrets_matched = 0
|
|
147
|
+
if options["all"]:
|
|
148
|
+
links_matched = ShareLink.objects.filter(revoked_at__isnull=True).count()
|
|
149
|
+
secrets_matched = BoardAutomation.objects.exclude(secret_ciphertext=b"").count()
|
|
150
|
+
self.stdout.write(f"Active share links matched: {links_matched}")
|
|
151
|
+
self.stdout.write(f"Configured git-automation secrets matched: {secrets_matched}")
|
|
152
|
+
|
|
153
|
+
if not doomed and not links_matched and not secrets_matched:
|
|
154
|
+
self.stdout.write(self.style.SUCCESS("Nothing to revoke."))
|
|
155
|
+
return
|
|
156
|
+
|
|
157
|
+
if not options["commit"]:
|
|
158
|
+
self.stdout.write(
|
|
159
|
+
self.style.WARNING("Dry run — nothing revoked. Re-run with --commit to apply.")
|
|
160
|
+
)
|
|
161
|
+
return
|
|
162
|
+
|
|
163
|
+
if not options["yes"]:
|
|
164
|
+
total = len(doomed) + links_matched + secrets_matched
|
|
165
|
+
answer = input(f"Revoke {total} grant(s)? This cannot be undone. [y/N] ")
|
|
166
|
+
if answer.strip().lower() not in ("y", "yes"):
|
|
167
|
+
raise CommandError("Aborted.")
|
|
168
|
+
|
|
169
|
+
now = timezone.now()
|
|
170
|
+
with transaction.atomic():
|
|
171
|
+
revoked = ApiToken.objects.filter(pk__in=[token.pk for token in doomed]).update(
|
|
172
|
+
revoked_at=now
|
|
173
|
+
)
|
|
174
|
+
# One audit row per token, in the same transaction as the revocation, so the
|
|
175
|
+
# sweep and its record commit together. The scope XOR on ApiTokenAuditEntry
|
|
176
|
+
# requires exactly one of owner/project/program, mirroring the token's own.
|
|
177
|
+
ApiTokenAuditEntry.objects.bulk_create(
|
|
178
|
+
[
|
|
179
|
+
ApiTokenAuditEntry(
|
|
180
|
+
owner_id=token.owner_id,
|
|
181
|
+
project_id=token.project_id,
|
|
182
|
+
program_id=token.program_id,
|
|
183
|
+
token=token,
|
|
184
|
+
token_prefix=token.token_prefix,
|
|
185
|
+
actor=None,
|
|
186
|
+
action=ApiTokenAuditAction.REVOKED.value,
|
|
187
|
+
detail={"name": token.name, "reason": REASON},
|
|
188
|
+
)
|
|
189
|
+
for token in doomed
|
|
190
|
+
]
|
|
191
|
+
)
|
|
192
|
+
links_revoked = secrets_cleared = 0
|
|
193
|
+
if options["all"]:
|
|
194
|
+
from trueppm_api.apps.access.services import revoke_personal_durable_grants
|
|
195
|
+
|
|
196
|
+
links_revoked, secrets_cleared = revoke_personal_durable_grants(actor=None)
|
|
197
|
+
|
|
198
|
+
self.stdout.write(self.style.SUCCESS(f"Revoked {revoked} token(s)."))
|
|
199
|
+
if options["all"]:
|
|
200
|
+
self.stdout.write(
|
|
201
|
+
self.style.SUCCESS(
|
|
202
|
+
f"Revoked {links_revoked} share link(s), cleared "
|
|
203
|
+
f"{secrets_cleared} git-automation secret(s)."
|
|
204
|
+
)
|
|
205
|
+
)
|
|
206
|
+
self.stdout.write(
|
|
207
|
+
"Sessions and JWTs are a separate credential class — rotate JWT_SIGNING_KEY "
|
|
208
|
+
"as well if you have not already (administration/security.md)."
|
|
209
|
+
)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Initial migration for the access app — creates ProjectMembership."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import uuid
|
|
6
|
+
|
|
7
|
+
import django.db.models.deletion
|
|
8
|
+
from django.conf import settings
|
|
9
|
+
from django.db import migrations, models
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Migration(migrations.Migration):
|
|
13
|
+
initial = True
|
|
14
|
+
|
|
15
|
+
dependencies: list[tuple[str, str]] = [ # type: ignore[misc]
|
|
16
|
+
("projects", "0001_initial"),
|
|
17
|
+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
operations = [
|
|
21
|
+
migrations.CreateModel(
|
|
22
|
+
name="ProjectMembership",
|
|
23
|
+
fields=[
|
|
24
|
+
("id", models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True)),
|
|
25
|
+
("server_version", models.BigIntegerField(default=0, editable=False)),
|
|
26
|
+
(
|
|
27
|
+
"project",
|
|
28
|
+
models.ForeignKey(
|
|
29
|
+
on_delete=django.db.models.deletion.PROTECT,
|
|
30
|
+
related_name="memberships",
|
|
31
|
+
to="projects.project",
|
|
32
|
+
),
|
|
33
|
+
),
|
|
34
|
+
(
|
|
35
|
+
"user",
|
|
36
|
+
models.ForeignKey(
|
|
37
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
38
|
+
related_name="memberships",
|
|
39
|
+
to=settings.AUTH_USER_MODEL,
|
|
40
|
+
),
|
|
41
|
+
),
|
|
42
|
+
(
|
|
43
|
+
"role",
|
|
44
|
+
models.IntegerField(
|
|
45
|
+
choices=[
|
|
46
|
+
(0, "Viewer"),
|
|
47
|
+
(1, "Member"),
|
|
48
|
+
(2, "Scheduler"),
|
|
49
|
+
(3, "Admin"),
|
|
50
|
+
(4, "Owner"),
|
|
51
|
+
]
|
|
52
|
+
),
|
|
53
|
+
),
|
|
54
|
+
],
|
|
55
|
+
options={"db_table": "access_project_membership"},
|
|
56
|
+
),
|
|
57
|
+
migrations.AlterUniqueTogether(
|
|
58
|
+
name="projectmembership",
|
|
59
|
+
unique_together={("project", "user")},
|
|
60
|
+
),
|
|
61
|
+
]
|