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,263 @@
|
|
|
1
|
+
"""Reusable dependency-graph validation for bulk / agent-authored task writes.
|
|
2
|
+
|
|
3
|
+
The human write path (``DependencySerializer._check_no_cycle``) runs
|
|
4
|
+
self-reference and cycle detection on every single proposed edge before it is
|
|
5
|
+
persisted. Bulk and agent write paths — the MS Project importer, the offline
|
|
6
|
+
Jira importer (#1664), the spreadsheet importer, and any future non-interactive
|
|
7
|
+
writer — build whole ``Dependency`` graphs with ``bulk_create`` and therefore
|
|
8
|
+
**bypass** that gate entirely.
|
|
9
|
+
|
|
10
|
+
Every importer named above now calls this guard before its write. The seed
|
|
11
|
+
importer (``apps/seed/importer.py``) still does not — tracked in #2589.
|
|
12
|
+
|
|
13
|
+
That gap is benign only while no bulk writer accepts an untrusted graph. The
|
|
14
|
+
moment a prospect's messy Jira export can be imported (#1664), a cyclic or
|
|
15
|
+
self-referential link would persist an infeasible dependency network that then
|
|
16
|
+
crashes the CPM / what-if engine on the imported data. The differentiator's
|
|
17
|
+
whole claim is that human and agent principals are governed identically; a
|
|
18
|
+
validation gate that only the human path enforces violates "hybrid by
|
|
19
|
+
construction."
|
|
20
|
+
|
|
21
|
+
This module is the shared gate. It wraps the *same* ``trueppm_scheduler``
|
|
22
|
+
``find_cycle`` algorithm the serializer uses — so the human and agent paths
|
|
23
|
+
detect the identical set of cycles — plus the trivial self-reference identity
|
|
24
|
+
check. Callers pass the *complete* edge set they are about to persist (they
|
|
25
|
+
already hold it in memory, so no DB round-trip is needed) and run this guard
|
|
26
|
+
**before** the write and before ``enqueue_recalculate``.
|
|
27
|
+
|
|
28
|
+
The guard operates on opaque string node ids, so callers may validate in their
|
|
29
|
+
own external-id space (Jira issue keys, MS Project uids) *before* creating any
|
|
30
|
+
row — the cycle/self-reference structure is invariant under relabeling, so a bad
|
|
31
|
+
graph is rejected before a single task is written.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
import re
|
|
37
|
+
from collections.abc import Mapping
|
|
38
|
+
|
|
39
|
+
from trueppm_scheduler import find_cycle
|
|
40
|
+
|
|
41
|
+
#: Longest one node label may run in a labelled message before it is elided.
|
|
42
|
+
MAX_LABEL_CHARS = 40
|
|
43
|
+
#: Most labels rendered along a cycle path; the rest collapse to "… (N more)".
|
|
44
|
+
#:
|
|
45
|
+
#: A cycle is a *set* of mutually-blocking tasks, so naming three or four of its
|
|
46
|
+
#: members and counting the rest identifies the loop as well as listing all of
|
|
47
|
+
#: them would — and a 200-task cycle must not produce a 200-name sentence.
|
|
48
|
+
MAX_CYCLE_LABELS = 4
|
|
49
|
+
#: Hard ceiling the two caps above are chosen to keep the whole sentence under.
|
|
50
|
+
#:
|
|
51
|
+
#: Not decoration: the classification popover's ``presentable()``
|
|
52
|
+
#: (``useClassificationPopover.ts``) *discards* a server message longer than 300
|
|
53
|
+
#: characters and shows its generic fallback instead, so an unbounded sentence
|
|
54
|
+
#: would silently undo this whole message. Asserted in the guard's tests.
|
|
55
|
+
#:
|
|
56
|
+
#: **Measured in UTF-16 code units, not code points**, because that is what the
|
|
57
|
+
#: check on the other side measures: JavaScript's ``String.length`` counts an
|
|
58
|
+
#: astral character (an emoji in a task name — ordinary, not adversarial) as two.
|
|
59
|
+
#: A code-point budget would pass here and be discarded there, which is the exact
|
|
60
|
+
#: failure this cap exists to prevent.
|
|
61
|
+
MAX_MESSAGE_CHARS = 300
|
|
62
|
+
|
|
63
|
+
#: C0 and C1 control characters, plus DEL.
|
|
64
|
+
_CONTROL_CHARS = re.compile(r"[\x00-\x1f\x7f-\x9f]")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _utf16_len(text: str) -> int:
|
|
68
|
+
"""Length as JavaScript's ``String.length`` would report it."""
|
|
69
|
+
return sum(2 if ord(ch) > 0xFFFF else 1 for ch in text)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _elide(text: str, limit: int) -> str:
|
|
73
|
+
"""Trim ``text`` to at most ``limit`` UTF-16 units, marking the cut with "…".
|
|
74
|
+
|
|
75
|
+
Iterates code points, so the cut can never land between the halves of a
|
|
76
|
+
surrogate pair and produce a lone surrogate.
|
|
77
|
+
"""
|
|
78
|
+
if _utf16_len(text) <= limit:
|
|
79
|
+
return text
|
|
80
|
+
kept: list[str] = []
|
|
81
|
+
width = 0
|
|
82
|
+
for ch in text:
|
|
83
|
+
char_width = 2 if ord(ch) > 0xFFFF else 1
|
|
84
|
+
if width + char_width > limit - 1:
|
|
85
|
+
break
|
|
86
|
+
kept.append(ch)
|
|
87
|
+
width += char_width
|
|
88
|
+
return "".join(kept).rstrip() + "…"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _labelled(node: str, labels: Mapping[str, str]) -> str:
|
|
92
|
+
"""A node's human label, falling back to its raw id when it has none.
|
|
93
|
+
|
|
94
|
+
A per-node fallback rather than an all-or-nothing one: a cycle whose members
|
|
95
|
+
are mostly resolvable is still worth naming, and a row deleted between the
|
|
96
|
+
refusal and the lookup must not cost the caller the other names.
|
|
97
|
+
|
|
98
|
+
Control characters are dropped and whitespace collapsed first. A label is
|
|
99
|
+
built from a task name, which is caller-controlled text that ``CharField``
|
|
100
|
+
does not stop from holding a newline, a tab, or an ANSI escape — and this
|
|
101
|
+
string is now *prose*, which a CLI or agent client is far likelier to print
|
|
102
|
+
raw than a JSON field. A name carrying its own line breaks could otherwise
|
|
103
|
+
fake the end of the message and append a sentence of its own.
|
|
104
|
+
"""
|
|
105
|
+
printable = _CONTROL_CHARS.sub(" ", labels.get(node, node))
|
|
106
|
+
return _elide(" ".join(printable.split()), MAX_LABEL_CHARS)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def nodes_to_label(offending: list[str]) -> list[str]:
|
|
110
|
+
"""The node ids a labelled message will actually name.
|
|
111
|
+
|
|
112
|
+
Exposed so a caller can resolve *only* the ids it is going to print. A cycle
|
|
113
|
+
is capped at :data:`MAX_CYCLE_LABELS` names, so resolving all of a 5000-task
|
|
114
|
+
loop would materialize 5000 rows to render four.
|
|
115
|
+
|
|
116
|
+
Deliberately lives beside :func:`_render_cycle` rather than being reproduced
|
|
117
|
+
at the call site: the two have to agree about which positions survive the
|
|
118
|
+
elision, and a caller re-deriving that slice locally is how they drift apart.
|
|
119
|
+
"""
|
|
120
|
+
if len(offending) <= MAX_CYCLE_LABELS:
|
|
121
|
+
return list(offending)
|
|
122
|
+
return [*offending[: MAX_CYCLE_LABELS - 1], offending[-1]]
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _render_cycle(offending: list[str], labels: Mapping[str, str]) -> str:
|
|
126
|
+
"""The cycle path as an arrow chain, elided in the middle when it is long.
|
|
127
|
+
|
|
128
|
+
``find_cycle`` repeats the first node at the end, and that repetition is what
|
|
129
|
+
makes the sentence read as a loop — so the last element is always kept and
|
|
130
|
+
the elision is taken out of the middle.
|
|
131
|
+
"""
|
|
132
|
+
if len(offending) <= MAX_CYCLE_LABELS:
|
|
133
|
+
return " → ".join(_labelled(node, labels) for node in offending)
|
|
134
|
+
head = offending[: MAX_CYCLE_LABELS - 1]
|
|
135
|
+
hidden = len(offending) - MAX_CYCLE_LABELS
|
|
136
|
+
parts = [_labelled(node, labels) for node in head]
|
|
137
|
+
parts.append(f"… ({hidden} more)")
|
|
138
|
+
parts.append(_labelled(offending[-1], labels))
|
|
139
|
+
return " → ".join(parts)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _build_message(reason: str, offending: list[str], labels: Mapping[str, str] | None) -> str:
|
|
143
|
+
"""The exception's message, human-facing when the caller supplied labels.
|
|
144
|
+
|
|
145
|
+
Without labels the original domain-signal message is kept verbatim. That is
|
|
146
|
+
what the importers validate in — they run the guard in their own external-id
|
|
147
|
+
space (Jira keys, MS Project uids) *before* any row exists, so there is no
|
|
148
|
+
task to name yet, and each already renders its own sentence from ``reason``
|
|
149
|
+
and ``offending`` (``msproject.importer.describe_bad_graph``).
|
|
150
|
+
|
|
151
|
+
Both branches tolerate an empty ``offending``. Neither is reachable from
|
|
152
|
+
:func:`validate_task_graph`, which always blames at least one node — but the
|
|
153
|
+
message builder must not be the thing that turns a refusal into a 500, or a
|
|
154
|
+
dangling separator, if a later caller constructs one.
|
|
155
|
+
"""
|
|
156
|
+
if labels is None:
|
|
157
|
+
return f"Infeasible task graph ({reason}): {offending}"
|
|
158
|
+
if reason == "self_reference":
|
|
159
|
+
node = _labelled(offending[0], labels) if offending else "A task"
|
|
160
|
+
return (
|
|
161
|
+
f"Task {node} lists itself as its own predecessor. "
|
|
162
|
+
"Remove that link to schedule this plan."
|
|
163
|
+
)
|
|
164
|
+
named = _render_cycle(offending, labels) if offending else "these tasks"
|
|
165
|
+
return f"Circular dependency: {named}. Remove one of those links to schedule this plan."
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class InfeasibleGraphError(Exception):
|
|
169
|
+
"""A proposed task-dependency graph cannot be persisted.
|
|
170
|
+
|
|
171
|
+
Deliberately not a DRF ``ValidationError``: this is a domain signal raised
|
|
172
|
+
from service / importer code that has no request context. Callers translate
|
|
173
|
+
it to whatever their surface needs — an importer marks its outbox row DEAD
|
|
174
|
+
and records the reason on the TaskRunTracker; a view would map it to a 400.
|
|
175
|
+
|
|
176
|
+
Attributes:
|
|
177
|
+
reason: A stable machine code — ``"self_reference"`` or
|
|
178
|
+
``"cyclic_dependency"`` — so callers can branch (quarantine a
|
|
179
|
+
self-loop vs. reject a whole import) without string-matching the
|
|
180
|
+
message.
|
|
181
|
+
offending: The node ids implicated. For ``"self_reference"`` the single
|
|
182
|
+
offending node; for ``"cyclic_dependency"`` the ordered cycle path
|
|
183
|
+
(first id repeated at the end, e.g. ``["A", "B", "A"]``), matching
|
|
184
|
+
:func:`trueppm_scheduler.find_cycle`.
|
|
185
|
+
labels: ``{node_id: human label}`` when a caller has resolved the ids to
|
|
186
|
+
something the person reading the refusal can find in their plan, or
|
|
187
|
+
``None`` when the message is still in id space. Only the *message*
|
|
188
|
+
reads this — ``offending`` stays the raw id list either way, because
|
|
189
|
+
that is what a client branches or highlights rows on.
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
def __init__(
|
|
193
|
+
self,
|
|
194
|
+
reason: str,
|
|
195
|
+
offending: list[str],
|
|
196
|
+
*,
|
|
197
|
+
labels: Mapping[str, str] | None = None,
|
|
198
|
+
) -> None:
|
|
199
|
+
self.reason = reason
|
|
200
|
+
self.offending = offending
|
|
201
|
+
self.labels: dict[str, str] | None = dict(labels) if labels is not None else None
|
|
202
|
+
super().__init__(_build_message(reason, offending, self.labels))
|
|
203
|
+
|
|
204
|
+
def with_labels(self, labels: Mapping[str, str]) -> InfeasibleGraphError:
|
|
205
|
+
"""This same refusal, re-worded to name its nodes rather than list their ids.
|
|
206
|
+
|
|
207
|
+
Returns a new instance rather than mutating in place so the raised object
|
|
208
|
+
stays immutable and the original stays available as the ``__cause__`` of
|
|
209
|
+
whatever the caller re-raises.
|
|
210
|
+
|
|
211
|
+
The guard itself operates on opaque node ids and cannot resolve them, so
|
|
212
|
+
labelling belongs to the caller that knows the id space. Doing it *after*
|
|
213
|
+
the refusal rather than passing labels in is deliberate: only the ids the
|
|
214
|
+
guard actually blamed need resolving, and the feasible path — every
|
|
215
|
+
request that is not refused — pays nothing at all.
|
|
216
|
+
"""
|
|
217
|
+
return InfeasibleGraphError(self.reason, self.offending, labels=labels)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def validate_task_graph(
|
|
221
|
+
edges: list[tuple[str, str]],
|
|
222
|
+
*,
|
|
223
|
+
children_map: dict[str, list[str]] | None = None,
|
|
224
|
+
) -> None:
|
|
225
|
+
"""Reject a self-referential or cyclic dependency graph before it is written.
|
|
226
|
+
|
|
227
|
+
Runs the same self-reference and :func:`trueppm_scheduler.find_cycle` checks
|
|
228
|
+
the interactive ``DependencySerializer`` path runs, so bulk / agent writers
|
|
229
|
+
are governed identically to the human write path.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
edges: The complete ``(predecessor_id, successor_id)`` set the caller is
|
|
233
|
+
about to persist, as opaque string node ids. Callers that want to
|
|
234
|
+
validate before minting DB rows may pass their external-id space
|
|
235
|
+
(e.g. Jira issue keys) — cycle/self-reference detection is invariant
|
|
236
|
+
under relabeling.
|
|
237
|
+
children_map: Optional ``{summary_id: [child_id, ...]}`` mapping so that
|
|
238
|
+
summary→leaf logical cycles are expanded and caught, matching the
|
|
239
|
+
serializer's summary handling. Omit (``None``) for flat graphs with
|
|
240
|
+
no summary tasks (the minimal-import case), where direct edge cycle
|
|
241
|
+
detection is sufficient.
|
|
242
|
+
|
|
243
|
+
Raises:
|
|
244
|
+
InfeasibleGraphError: With ``reason="self_reference"`` if any edge links
|
|
245
|
+
a task to itself, or ``reason="cyclic_dependency"`` if the graph
|
|
246
|
+
contains a cycle. The message and ``offending`` path let the caller
|
|
247
|
+
surface an actionable error rather than crash the CPM engine.
|
|
248
|
+
InvalidScheduleInput: Re-raised unchanged if the graph is *malformed*
|
|
249
|
+
(a pathological summary fan-out beyond the engine's cap, or a
|
|
250
|
+
children_map that is itself cyclic) — a distinct failure from a
|
|
251
|
+
cycle in the edges. Callers treat it the same as a rejection.
|
|
252
|
+
"""
|
|
253
|
+
# Self-reference is a cheap identity check that also gives the caller a
|
|
254
|
+
# precise offending node (find_cycle would report it as a length-1 loop,
|
|
255
|
+
# but a dedicated code lets importers *quarantine* a self-loop — skip it
|
|
256
|
+
# with a warning — rather than reject the whole import).
|
|
257
|
+
for predecessor, successor in edges:
|
|
258
|
+
if predecessor == successor:
|
|
259
|
+
raise InfeasibleGraphError("self_reference", [predecessor])
|
|
260
|
+
|
|
261
|
+
result = find_cycle(edges, children_map=children_map)
|
|
262
|
+
if result.cycle is not None:
|
|
263
|
+
raise InfeasibleGraphError("cyclic_dependency", result.cycle)
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Apply the tiered retention curve to project forecast snapshots (ADR-0154, #388).
|
|
2
|
+
|
|
3
|
+
Operator-facing wrapper around the same ``_do_prune_forecast_snapshots`` logic the
|
|
4
|
+
nightly ``scheduling.prune_forecast_snapshots`` Celery Beat task runs. Per
|
|
5
|
+
``settings.FORECAST_SNAPSHOT_RETENTION``: keep all rows younger than ``daily_days``
|
|
6
|
+
(default 90); keep one-per-ISO-week up to ``weekly_days`` (default 365); keep
|
|
7
|
+
one-per-calendar-month beyond that (kept forever).
|
|
8
|
+
|
|
9
|
+
Idempotent: a second run deletes nothing new (the keepers are already the only rows
|
|
10
|
+
left in each bucket).
|
|
11
|
+
|
|
12
|
+
Usage::
|
|
13
|
+
|
|
14
|
+
python manage.py prune_forecast_snapshots # apply
|
|
15
|
+
python manage.py prune_forecast_snapshots --dry-run # report only, no delete
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import logging
|
|
21
|
+
from typing import Any
|
|
22
|
+
|
|
23
|
+
from django.core.management.base import BaseCommand
|
|
24
|
+
|
|
25
|
+
from trueppm_api.apps.scheduling.models import ProjectForecastSnapshot
|
|
26
|
+
from trueppm_api.apps.scheduling.tasks import _do_prune_forecast_snapshots
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Command(BaseCommand):
|
|
32
|
+
help = "Prune project forecast snapshots per the tiered retention policy (ADR-0154)."
|
|
33
|
+
|
|
34
|
+
def add_arguments(self, parser: Any) -> None:
|
|
35
|
+
parser.add_argument(
|
|
36
|
+
"--dry-run",
|
|
37
|
+
action="store_true",
|
|
38
|
+
help="Report how many rows would be deleted without deleting them.",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
def handle(self, *args: Any, **options: Any) -> None:
|
|
42
|
+
if options["dry_run"]:
|
|
43
|
+
# Mirror the task's bucketing in a counting-only pass: total - keepers.
|
|
44
|
+
# Re-using the live function would delete, so we report the current count
|
|
45
|
+
# and let the operator run without --dry-run to apply.
|
|
46
|
+
total = ProjectForecastSnapshot.objects.count()
|
|
47
|
+
self.stdout.write(
|
|
48
|
+
f"{total} forecast snapshot(s) on record; run without --dry-run to prune."
|
|
49
|
+
)
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
deleted = _do_prune_forecast_snapshots()
|
|
53
|
+
self.stdout.write(self.style.SUCCESS(f"Pruned {deleted} forecast snapshot(s)."))
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"""Request-path admission control for synchronous Monte Carlo runs (#2839).
|
|
2
|
+
|
|
3
|
+
Two request-path endpoints run ``trueppm_scheduler.monte_carlo()`` inline:
|
|
4
|
+
``run_monte_carlo`` (one simulation) and ``MonteCarloWhatIfView`` (two, back to
|
|
5
|
+
back). The engine caps ``runs`` (``MC_SIMULATION_CAP``) and ``n_tasks``
|
|
6
|
+
(``MC_TASK_CAP``) separately, but the simulation's working set is three live
|
|
7
|
+
``(runs, n_tasks)`` float64 matrices plus temporaries, so what it costs — in
|
|
8
|
+
memory *and*, since the hot path is vectorized and linear in it, in wall time — is
|
|
9
|
+
the **product** ``runs x n_tasks``. Capping each dimension leaves the product at
|
|
10
|
+
5,000 x 1,000 = 5M cells, measured at ~234 MB traced / ~336 MB RSS per call, on a
|
|
11
|
+
single default API worker that also serves every WebSocket. This module caps the
|
|
12
|
+
product (``MC_CELL_CAP``) and the number of simulations one process runs at once
|
|
13
|
+
(``MC_MAX_CONCURRENT``).
|
|
14
|
+
|
|
15
|
+
Both are memory-safety bounds, not edition gates: refusals here are 400 / 503,
|
|
16
|
+
never the 402 the per-dimension OSS caps return.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import threading
|
|
22
|
+
from collections.abc import Iterator
|
|
23
|
+
from contextlib import contextmanager
|
|
24
|
+
from dataclasses import dataclass
|
|
25
|
+
|
|
26
|
+
from django.conf import settings
|
|
27
|
+
from rest_framework import status
|
|
28
|
+
from rest_framework.response import Response
|
|
29
|
+
|
|
30
|
+
# How long a request waits for a free simulation slot before it is refused. Long
|
|
31
|
+
# enough to ride out one in-flight run at the cell cap (a few seconds), short of
|
|
32
|
+
# any ingress timeout, so a brief collision queues instead of failing.
|
|
33
|
+
MC_SLOT_WAIT_SECONDS = 10.0
|
|
34
|
+
|
|
35
|
+
# Retry-After on a 503 busy refusal — about one simulation's duration at the cap.
|
|
36
|
+
MC_BUSY_RETRY_AFTER_SECONDS = 5
|
|
37
|
+
|
|
38
|
+
_slot_lock = threading.Lock()
|
|
39
|
+
_slot_semaphore: threading.BoundedSemaphore | None = None
|
|
40
|
+
_slot_semaphore_size: int | None = None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True)
|
|
44
|
+
class McAdmission:
|
|
45
|
+
"""The outcome of :func:`admit_runs`.
|
|
46
|
+
|
|
47
|
+
Exactly one of ``runs`` / ``refusal`` is meaningful: ``refusal`` is a ready
|
|
48
|
+
400 response when the request cannot be admitted, else ``None`` and ``runs`` is
|
|
49
|
+
the iteration count to simulate.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
runs: int
|
|
53
|
+
refusal: Response | None = None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def max_runs_for(n_tasks: int) -> int | None:
|
|
57
|
+
"""Largest iteration count admissible for a network of ``n_tasks`` leaf tasks.
|
|
58
|
+
|
|
59
|
+
``min(MC_SIMULATION_CAP, MC_CELL_CAP // n_tasks)``, where either cap may be
|
|
60
|
+
``None`` (unbounded). ``None`` means no bound at all. Can be ``0`` when the
|
|
61
|
+
task count alone exceeds the cell cap.
|
|
62
|
+
"""
|
|
63
|
+
sim_cap: int | None = settings.MC_SIMULATION_CAP
|
|
64
|
+
cell_cap: int | None = getattr(settings, "MC_CELL_CAP", None)
|
|
65
|
+
bounds: list[int] = []
|
|
66
|
+
if sim_cap is not None:
|
|
67
|
+
bounds.append(sim_cap)
|
|
68
|
+
if cell_cap is not None and n_tasks > 0:
|
|
69
|
+
bounds.append(cell_cap // n_tasks)
|
|
70
|
+
return min(bounds) if bounds else None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def admit_runs(n_tasks: int, requested: int | None) -> McAdmission:
|
|
74
|
+
"""Resolve the iteration count for a request, applying the cell cap.
|
|
75
|
+
|
|
76
|
+
* ``requested is None`` (the caller left ``n_simulations`` out — the web
|
|
77
|
+
client's default): run the admissible maximum for this project's size. On a
|
|
78
|
+
large project that is fewer than ``MC_SIMULATION_CAP``; the response's
|
|
79
|
+
``runs`` field reports the count actually simulated, so the down-scale is
|
|
80
|
+
never hidden.
|
|
81
|
+
* An explicit ``requested`` above the size-derived maximum is **refused**
|
|
82
|
+
(400, ``simulation_cell_cap_exceeded``, carrying ``max_runs``) rather than
|
|
83
|
+
silently shrunk: a caller that asked for N samples must not get a forecast
|
|
84
|
+
computed from fewer while believing it got N.
|
|
85
|
+
|
|
86
|
+
Two cases are deliberately passed through to the engine unchanged, so their
|
|
87
|
+
existing 402 refusal (and its message) stays exactly what it was: a project
|
|
88
|
+
over ``MC_TASK_CAP``, and a request above ``MC_SIMULATION_CAP``.
|
|
89
|
+
"""
|
|
90
|
+
sim_cap: int | None = settings.MC_SIMULATION_CAP
|
|
91
|
+
task_cap: int | None = settings.MC_TASK_CAP
|
|
92
|
+
default_runs = sim_cap or 1_000
|
|
93
|
+
|
|
94
|
+
if task_cap is not None and n_tasks > task_cap:
|
|
95
|
+
return McAdmission(runs=requested if requested is not None else default_runs)
|
|
96
|
+
if requested is not None and sim_cap is not None and requested > sim_cap:
|
|
97
|
+
return McAdmission(runs=requested)
|
|
98
|
+
|
|
99
|
+
max_runs = max_runs_for(n_tasks)
|
|
100
|
+
if max_runs is None:
|
|
101
|
+
return McAdmission(runs=requested if requested is not None else default_runs)
|
|
102
|
+
|
|
103
|
+
runs = requested if requested is not None else max_runs
|
|
104
|
+
if runs > max_runs or runs < 1:
|
|
105
|
+
return McAdmission(
|
|
106
|
+
runs=0,
|
|
107
|
+
refusal=Response(
|
|
108
|
+
{
|
|
109
|
+
"error": "simulation_cell_cap_exceeded",
|
|
110
|
+
"message": (
|
|
111
|
+
f"{runs} simulation runs over {n_tasks} tasks exceeds this "
|
|
112
|
+
f"server's Monte Carlo size limit (MC_CELL_CAP); at most "
|
|
113
|
+
f"{max_runs} runs are admissible for a project this size."
|
|
114
|
+
),
|
|
115
|
+
"max_runs": max_runs,
|
|
116
|
+
"task_count": n_tasks,
|
|
117
|
+
},
|
|
118
|
+
status=status.HTTP_400_BAD_REQUEST,
|
|
119
|
+
),
|
|
120
|
+
)
|
|
121
|
+
return McAdmission(runs=runs)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _semaphore() -> threading.BoundedSemaphore | None:
|
|
125
|
+
"""The process-wide simulation slot pool, (re)built if the setting changed.
|
|
126
|
+
|
|
127
|
+
Rebuilt on a size change so a settings override takes effect; never rebuilt
|
|
128
|
+
while the size is stable, so held slots are always released to the pool they
|
|
129
|
+
came from.
|
|
130
|
+
"""
|
|
131
|
+
global _slot_semaphore, _slot_semaphore_size
|
|
132
|
+
size: int | None = getattr(settings, "MC_MAX_CONCURRENT", None)
|
|
133
|
+
if size is None:
|
|
134
|
+
return None
|
|
135
|
+
with _slot_lock:
|
|
136
|
+
if _slot_semaphore is None or _slot_semaphore_size != size:
|
|
137
|
+
_slot_semaphore = threading.BoundedSemaphore(max(1, size))
|
|
138
|
+
_slot_semaphore_size = size
|
|
139
|
+
return _slot_semaphore
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def busy_response() -> Response:
|
|
143
|
+
"""503 returned when no simulation slot frees up within the wait window."""
|
|
144
|
+
return Response(
|
|
145
|
+
{
|
|
146
|
+
"error": "simulation_busy",
|
|
147
|
+
"message": (
|
|
148
|
+
"The server is already running its maximum number of concurrent "
|
|
149
|
+
"Monte Carlo simulations. Retry in a few seconds."
|
|
150
|
+
),
|
|
151
|
+
},
|
|
152
|
+
status=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
153
|
+
headers={"Retry-After": str(MC_BUSY_RETRY_AFTER_SECONDS)},
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@contextmanager
|
|
158
|
+
def simulation_slot() -> Iterator[bool]:
|
|
159
|
+
"""Hold one of this process's ``MC_MAX_CONCURRENT`` simulation slots.
|
|
160
|
+
|
|
161
|
+
Yields ``True`` once a slot is held, or ``False`` if none freed up within
|
|
162
|
+
:data:`MC_SLOT_WAIT_SECONDS` — the caller then returns :func:`busy_response`
|
|
163
|
+
without simulating. A sync Django view under ASGI runs on its own thread per
|
|
164
|
+
request, so concurrent simulations in one worker are real and a thread
|
|
165
|
+
semaphore bounds them. The bound is per process: N API workers admit up to
|
|
166
|
+
N x ``MC_MAX_CONCURRENT`` simulations, which is the right unit because each
|
|
167
|
+
worker has its own memory limit to protect.
|
|
168
|
+
|
|
169
|
+
The what-if view holds a single slot across both of its simulations — they
|
|
170
|
+
run sequentially, so one slot is the honest cost.
|
|
171
|
+
"""
|
|
172
|
+
sem = _semaphore()
|
|
173
|
+
if sem is None:
|
|
174
|
+
yield True
|
|
175
|
+
return
|
|
176
|
+
acquired = sem.acquire(timeout=MC_SLOT_WAIT_SECONDS)
|
|
177
|
+
try:
|
|
178
|
+
yield acquired
|
|
179
|
+
finally:
|
|
180
|
+
if acquired:
|
|
181
|
+
sem.release()
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Create FailedTask model for dead-letter tracking."""
|
|
2
|
+
|
|
3
|
+
import uuid
|
|
4
|
+
|
|
5
|
+
from django.db import migrations, models
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Migration(migrations.Migration):
|
|
9
|
+
initial = True
|
|
10
|
+
|
|
11
|
+
dependencies = []
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.CreateModel(
|
|
15
|
+
name="FailedTask",
|
|
16
|
+
fields=[
|
|
17
|
+
(
|
|
18
|
+
"id",
|
|
19
|
+
models.UUIDField(
|
|
20
|
+
default=uuid.uuid4,
|
|
21
|
+
editable=False,
|
|
22
|
+
primary_key=True,
|
|
23
|
+
serialize=False,
|
|
24
|
+
),
|
|
25
|
+
),
|
|
26
|
+
("task_name", models.CharField(db_index=True, max_length=255)),
|
|
27
|
+
("task_id", models.CharField(max_length=255, unique=True)),
|
|
28
|
+
("args", models.JSONField(default=list)),
|
|
29
|
+
("kwargs", models.JSONField(default=dict)),
|
|
30
|
+
("exception_type", models.CharField(max_length=255)),
|
|
31
|
+
("exception_message", models.TextField()),
|
|
32
|
+
("traceback", models.TextField()),
|
|
33
|
+
("failure_count", models.PositiveIntegerField(default=1)),
|
|
34
|
+
("first_failed_at", models.DateTimeField(auto_now_add=True)),
|
|
35
|
+
("last_failed_at", models.DateTimeField(auto_now=True)),
|
|
36
|
+
(
|
|
37
|
+
"status",
|
|
38
|
+
models.CharField(
|
|
39
|
+
choices=[
|
|
40
|
+
("pending_retry", "Pending Retry"),
|
|
41
|
+
("dead", "Dead"),
|
|
42
|
+
("dismissed", "Dismissed"),
|
|
43
|
+
("retried", "Retried"),
|
|
44
|
+
],
|
|
45
|
+
db_index=True,
|
|
46
|
+
default="dead",
|
|
47
|
+
max_length=16,
|
|
48
|
+
),
|
|
49
|
+
),
|
|
50
|
+
],
|
|
51
|
+
options={
|
|
52
|
+
"ordering": ["-last_failed_at"],
|
|
53
|
+
},
|
|
54
|
+
),
|
|
55
|
+
migrations.AddIndex(
|
|
56
|
+
model_name="failedtask",
|
|
57
|
+
index=models.Index(
|
|
58
|
+
fields=["status", "last_failed_at"],
|
|
59
|
+
name="failed_task_status_idx",
|
|
60
|
+
),
|
|
61
|
+
),
|
|
62
|
+
]
|