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,120 @@
|
|
|
1
|
+
"""Re-anchor bundled sample programs to today (#3481, ADR-1175).
|
|
2
|
+
|
|
3
|
+
Usage::
|
|
4
|
+
|
|
5
|
+
python manage.py shift_sample_dates [--program <id>] [--dry-run]
|
|
6
|
+
|
|
7
|
+
The command-line half of the "Shift dates to today" banner action. It exists so a
|
|
8
|
+
long-lived demo deployment can keep its sample current from a scheduled job it
|
|
9
|
+
owns — a Kubernetes CronJob alongside the existing demo-seed Job, say — rather
|
|
10
|
+
than every self-hosted install acquiring a Celery beat that silently rewrites
|
|
11
|
+
rows nobody asked it to touch.
|
|
12
|
+
|
|
13
|
+
That is the deliberate shape of ADR-1175 Decision 1: **no global beat ships**. An
|
|
14
|
+
operator who wants automatic re-anchoring opts into it visibly, in their own
|
|
15
|
+
manifests, where a failure is theirs to see.
|
|
16
|
+
|
|
17
|
+
Idempotent, like the endpoint: a sample already within a week of today reports
|
|
18
|
+
"already current" and writes nothing, so running this nightly is safe.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
from django.core.management.base import BaseCommand, CommandError
|
|
26
|
+
|
|
27
|
+
from trueppm_api.apps.projects.models import Program, Project
|
|
28
|
+
from trueppm_api.apps.projects.seed.reanchor import (
|
|
29
|
+
NoAnchorRecorded,
|
|
30
|
+
NotASampleProgram,
|
|
31
|
+
shift_sample_dates,
|
|
32
|
+
whole_week_delta,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Command(BaseCommand):
|
|
37
|
+
help = "Shift bundled sample programs' dates forward to today."
|
|
38
|
+
|
|
39
|
+
def add_arguments(self, parser: Any) -> None:
|
|
40
|
+
parser.add_argument(
|
|
41
|
+
"--program",
|
|
42
|
+
help="Shift only this program id. Default: every sample program on the install.",
|
|
43
|
+
)
|
|
44
|
+
parser.add_argument(
|
|
45
|
+
"--dry-run",
|
|
46
|
+
action="store_true",
|
|
47
|
+
help="Report what would move without writing anything.",
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def handle(self, *args: Any, **options: Any) -> None:
|
|
51
|
+
from django.utils import timezone
|
|
52
|
+
|
|
53
|
+
programs = self._resolve_programs(options.get("program"))
|
|
54
|
+
if not programs:
|
|
55
|
+
self.stdout.write("No sample programs found; nothing to do.")
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
today = timezone.localdate()
|
|
59
|
+
for program in programs:
|
|
60
|
+
label = f"{program.name!r} ({program.pk})"
|
|
61
|
+
if options["dry_run"]:
|
|
62
|
+
self._report_dry_run(program, label, today)
|
|
63
|
+
else:
|
|
64
|
+
self._shift(program, label)
|
|
65
|
+
|
|
66
|
+
def _report_dry_run(self, program: Program, label: str, today: Any) -> None:
|
|
67
|
+
# Reported from the same helper the shift uses, so the dry run
|
|
68
|
+
# cannot disagree with the real thing about how far it would move.
|
|
69
|
+
if program.sample_anchor_date is None:
|
|
70
|
+
self.stdout.write(
|
|
71
|
+
self.style.WARNING(f" {label}: no anchor recorded — reload it instead.")
|
|
72
|
+
)
|
|
73
|
+
return
|
|
74
|
+
days = whole_week_delta(program.sample_anchor_date, today)
|
|
75
|
+
if days == 0:
|
|
76
|
+
self.stdout.write(f" {label}: already current.")
|
|
77
|
+
else:
|
|
78
|
+
self.stdout.write(f" {label}: would move forward {days} days.")
|
|
79
|
+
|
|
80
|
+
def _shift(self, program: Program, label: str) -> None:
|
|
81
|
+
try:
|
|
82
|
+
report = shift_sample_dates(program)
|
|
83
|
+
except NotASampleProgram as exc:
|
|
84
|
+
# Only reachable via --program: the default query already filters
|
|
85
|
+
# to programs holding a sample project.
|
|
86
|
+
raise CommandError(f"{label}: {exc}") from exc
|
|
87
|
+
except NoAnchorRecorded:
|
|
88
|
+
self.stdout.write(
|
|
89
|
+
self.style.WARNING(
|
|
90
|
+
f" {label}: loaded before date shifting existed, so its original "
|
|
91
|
+
f"dates are not recorded. Remove and reload it to get current dates."
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
if report.shifted:
|
|
97
|
+
self.stdout.write(
|
|
98
|
+
self.style.SUCCESS(
|
|
99
|
+
f" {label}: moved forward {report.days} days — "
|
|
100
|
+
f"{report.rows_shifted} records across {report.projects} project(s). "
|
|
101
|
+
f"Schedules re-queued."
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
else:
|
|
105
|
+
self.stdout.write(f" {label}: already current.")
|
|
106
|
+
|
|
107
|
+
def _resolve_programs(self, program_id: str | None) -> list[Program]:
|
|
108
|
+
if program_id:
|
|
109
|
+
program = Program.objects.filter(pk=program_id).first()
|
|
110
|
+
if program is None:
|
|
111
|
+
raise CommandError(f"No program with id {program_id!r}.")
|
|
112
|
+
return [program]
|
|
113
|
+
# Sample-ness lives on Project, not Program — a program is demo data when
|
|
114
|
+
# it holds at least one is_sample project (the same test remove_sample and
|
|
115
|
+
# the banner use).
|
|
116
|
+
return list(
|
|
117
|
+
Program.objects.filter(
|
|
118
|
+
pk__in=Project.objects.filter(is_sample=True, is_deleted=False).values("program")
|
|
119
|
+
).order_by("name")
|
|
120
|
+
)
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
"""Instance → Workspace → Program → Project MCP read-consent resolution (ADR-0678, #2482).
|
|
2
|
+
|
|
3
|
+
One inheritable setting governs whether an MCP/agent token may read a project's
|
|
4
|
+
data: ``mcp_enabled``. The Workspace root is non-null (default ``True``); Program
|
|
5
|
+
and Project are nullable, where ``None`` means *no opinion* — **never "yes"**.
|
|
6
|
+
|
|
7
|
+
**The cascade is restrictive-only (AND), not override.** This is the one place
|
|
8
|
+
this module deliberately diverges from the ADR-0135/0144/0151/0153/0510 settings
|
|
9
|
+
family, and the divergence is the point::
|
|
10
|
+
|
|
11
|
+
effective(project) = settings.TRUEPPM_MCP_ENABLED # ADR-0497 instance switch
|
|
12
|
+
AND workspace.mcp_enabled
|
|
13
|
+
AND (program.mcp_enabled is not False)
|
|
14
|
+
AND (project.mcp_enabled is not False)
|
|
15
|
+
|
|
16
|
+
Any scope may deny for itself and everything beneath it; **no scope may grant over
|
|
17
|
+
another scope's denial**. For sharing and attachments a workspace ``ENFORCE`` lock
|
|
18
|
+
is a security *ceiling* and pointing it downward is coherent. For a **consent**
|
|
19
|
+
switch it is not: an org-level lock that could force MCP back on for a team is
|
|
20
|
+
precisely the *"consent that only an admin can grant or revoke on the team's
|
|
21
|
+
behalf is consent in name only"* objection (#2415) this control exists to answer.
|
|
22
|
+
|
|
23
|
+
Consequently there is **no** ``mcp_override_policy`` field and **no** Enterprise
|
|
24
|
+
enforcement-provider seam here, unlike :mod:`~trueppm_api.apps.projects.attachment_policy`.
|
|
25
|
+
That asymmetry is deliberate, not an oversight. Enterprise governance keeps the
|
|
26
|
+
only lever that is coherent for a consent control — turning MCP off at the
|
|
27
|
+
workspace scope, org-wide — which the AND already honors.
|
|
28
|
+
|
|
29
|
+
The one policy knob that does exist, ``TRUEPPM_MCP_PROGRAM_EXPORT_POLICY``
|
|
30
|
+
(:func:`program_export_policy`, #3014), is **operator-level and not a scope in this
|
|
31
|
+
cascade**. It answers a question the AND above cannot: a program-level *bulk export*
|
|
32
|
+
carries every member project's rows verbatim in one artifact that cannot be narrowed,
|
|
33
|
+
so the choice is serve-or-refuse rather than filter. Keeping it in deployment
|
|
34
|
+
settings rather than on ``Workspace`` or ``Program`` is what stops it from becoming
|
|
35
|
+
the tenant-scope override this module refuses — an operator is the person running
|
|
36
|
+
the server, not a scope above the team inside it.
|
|
37
|
+
|
|
38
|
+
Resolution is computed-on-read (ADR-0108); there is no denormalized effective
|
|
39
|
+
column to keep in sync. Clients read the serializer's ``effective_mcp_enabled`` /
|
|
40
|
+
``inherited_mcp_enabled`` fields and never re-implement this precedence.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
from __future__ import annotations
|
|
44
|
+
|
|
45
|
+
from typing import TYPE_CHECKING, Any
|
|
46
|
+
|
|
47
|
+
if TYPE_CHECKING:
|
|
48
|
+
from django.db.models import QuerySet
|
|
49
|
+
from rest_framework.request import Request
|
|
50
|
+
|
|
51
|
+
from trueppm_api.apps.projects.models import Program, Project
|
|
52
|
+
from trueppm_api.apps.workspace.models import Workspace
|
|
53
|
+
|
|
54
|
+
#: The single inheritable field name — identical on Workspace (non-null root),
|
|
55
|
+
#: Program, and Project (nullable "no opinion").
|
|
56
|
+
MCP_CONSENT_FIELD = "mcp_enabled"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _instance_enabled() -> bool:
|
|
60
|
+
"""The ADR-0497 instance-wide kill switch, read at call time.
|
|
61
|
+
|
|
62
|
+
Read through ``getattr`` at call time (not import time) so ``override_settings``
|
|
63
|
+
and a live operator env change both take effect, matching ``McpInstanceEnabled``.
|
|
64
|
+
"""
|
|
65
|
+
from django.conf import settings
|
|
66
|
+
|
|
67
|
+
return bool(getattr(settings, "TRUEPPM_MCP_ENABLED", True))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _program_of(obj: Program | Project) -> Program | None:
|
|
71
|
+
"""The program scope above ``obj`` (a Project's program), or None."""
|
|
72
|
+
from trueppm_api.apps.projects.models import Program
|
|
73
|
+
|
|
74
|
+
if not isinstance(obj, Program) and getattr(obj, "program_id", None):
|
|
75
|
+
return obj.program
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def resolve_mcp_enabled(
|
|
80
|
+
obj: Program | Project,
|
|
81
|
+
*,
|
|
82
|
+
workspace: Workspace | None = None,
|
|
83
|
+
include_instance: bool = True,
|
|
84
|
+
) -> bool:
|
|
85
|
+
"""Resolve effective MCP read consent for a program or project.
|
|
86
|
+
|
|
87
|
+
ANDs every scope: instance → workspace → program → project. A ``None`` at the
|
|
88
|
+
program or project scope contributes nothing (no opinion); only an explicit
|
|
89
|
+
``False`` denies.
|
|
90
|
+
|
|
91
|
+
``workspace`` may be passed to avoid re-loading the singleton per object when
|
|
92
|
+
resolving a list (the serializer caches it once); otherwise it is loaded here.
|
|
93
|
+
|
|
94
|
+
``include_instance=False`` omits the ADR-0497 instance switch, for callers that
|
|
95
|
+
want the *team's* answer alone — the settings UI shows the team's decision even
|
|
96
|
+
on an instance where MCP is globally off, so the two switches stay legible as
|
|
97
|
+
separate facts rather than collapsing into one confusing "off".
|
|
98
|
+
"""
|
|
99
|
+
from trueppm_api.apps.workspace.models import Workspace
|
|
100
|
+
|
|
101
|
+
if include_instance and not _instance_enabled():
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
if workspace is None:
|
|
105
|
+
workspace = Workspace.load()
|
|
106
|
+
if not workspace.mcp_enabled:
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
return _parent_and_own_enabled(obj)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def resolve_inherited_mcp_enabled(
|
|
113
|
+
obj: Program | Project,
|
|
114
|
+
*,
|
|
115
|
+
workspace: Workspace | None = None,
|
|
116
|
+
) -> bool:
|
|
117
|
+
"""Value ``obj`` would resolve to if its own override were cleared to *no opinion*.
|
|
118
|
+
|
|
119
|
+
Drives the settings "Inherit (On/Off)" affordance — it deliberately skips
|
|
120
|
+
``obj``'s own value and resolves from the parent scopes up. The instance switch
|
|
121
|
+
is excluded for the same legibility reason as
|
|
122
|
+
:func:`resolve_mcp_enabled`'s ``include_instance=False``.
|
|
123
|
+
"""
|
|
124
|
+
from trueppm_api.apps.workspace.models import Workspace
|
|
125
|
+
|
|
126
|
+
if workspace is None:
|
|
127
|
+
workspace = Workspace.load()
|
|
128
|
+
if not workspace.mcp_enabled:
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
program = _program_of(obj)
|
|
132
|
+
return not (program is not None and program.mcp_enabled is False)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _parent_and_own_enabled(obj: Program | Project) -> bool:
|
|
136
|
+
"""AND of the program scope (if any) and ``obj``'s own value.
|
|
137
|
+
|
|
138
|
+
Tri-state: the test is ``is False``, never truthiness — ``None`` is "no
|
|
139
|
+
opinion" and must not be read as a denial.
|
|
140
|
+
"""
|
|
141
|
+
program = _program_of(obj)
|
|
142
|
+
if program is not None and program.mcp_enabled is False:
|
|
143
|
+
return False
|
|
144
|
+
return obj.mcp_enabled is not False
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def is_mcp_readable(
|
|
148
|
+
obj: Program | Project,
|
|
149
|
+
*,
|
|
150
|
+
workspace: Workspace | None = None,
|
|
151
|
+
) -> bool:
|
|
152
|
+
"""The single enforcement predicate: may an agent token read ``obj``'s data?"""
|
|
153
|
+
return resolve_mcp_enabled(obj, workspace=workspace)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def mcp_opted_out_project_ids() -> QuerySet[Any]:
|
|
157
|
+
"""Project ids closed to agent reads by *their own or their program's* denial.
|
|
158
|
+
|
|
159
|
+
Returns a lazy values-list queryset so callers can use it directly as an
|
|
160
|
+
``__in`` subquery without a second round trip. Deliberately does **not**
|
|
161
|
+
consider the workspace or instance switch: those are all-or-nothing and are
|
|
162
|
+
short-circuited by the caller (see :func:`mcp_reads_globally_disabled`) rather
|
|
163
|
+
than expanded into a project-id set, which at scale would mean listing every
|
|
164
|
+
project on the instance.
|
|
165
|
+
"""
|
|
166
|
+
from django.db.models import Q
|
|
167
|
+
|
|
168
|
+
from trueppm_api.apps.projects.models import Project
|
|
169
|
+
|
|
170
|
+
return Project.objects.filter(Q(mcp_enabled=False) | Q(program__mcp_enabled=False)).values_list(
|
|
171
|
+
"pk", flat=True
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
#: Policy values for :data:`django.conf.settings.TRUEPPM_MCP_PROGRAM_EXPORT_POLICY`.
|
|
176
|
+
PROGRAM_EXPORT_WITHHOLD = "withhold"
|
|
177
|
+
PROGRAM_EXPORT_ALLOW = "allow"
|
|
178
|
+
PROGRAM_EXPORT_POLICIES = frozenset({PROGRAM_EXPORT_WITHHOLD, PROGRAM_EXPORT_ALLOW})
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def program_export_policy() -> str:
|
|
182
|
+
"""The operator's program-bulk-export policy, read at call time (#3014).
|
|
183
|
+
|
|
184
|
+
Unrecognized values fall back to ``withhold`` rather than raising: this is read
|
|
185
|
+
on a request path, and a typo in an env var must not turn a consent control off
|
|
186
|
+
by accident. A boot-time check (``core.checks``) surfaces the typo loudly so the
|
|
187
|
+
fallback is a safety net rather than a place mistakes go to hide.
|
|
188
|
+
"""
|
|
189
|
+
from django.conf import settings
|
|
190
|
+
|
|
191
|
+
value = str(getattr(settings, "TRUEPPM_MCP_PROGRAM_EXPORT_POLICY", PROGRAM_EXPORT_WITHHOLD))
|
|
192
|
+
normalized = value.strip().lower()
|
|
193
|
+
return normalized if normalized in PROGRAM_EXPORT_POLICIES else PROGRAM_EXPORT_WITHHOLD
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def program_export_withheld_from_agents(program: Program) -> bool:
|
|
197
|
+
"""Whether an agent token must be refused this program's bulk export (#3014).
|
|
198
|
+
|
|
199
|
+
A program-level bulk export — the sync JSON seed (``GET /programs/{id}/export/``)
|
|
200
|
+
and the async ``.tar.gz`` bundle — carries **every member project's data
|
|
201
|
+
verbatim**. The mixin's ``Program`` branch in ``_mcp_filter_queryset`` governs
|
|
202
|
+
only ``program.mcp_enabled``, so without this a child project's explicit "no" is
|
|
203
|
+
silently bypassed by reading the parent's export instead. Read through the
|
|
204
|
+
project's own endpoints, the same rows are withheld.
|
|
205
|
+
|
|
206
|
+
**Why withhold rather than narrow.** Neither surface can honestly serve a
|
|
207
|
+
subset. The ``.tar.gz`` is built asynchronously and streamed from storage as
|
|
208
|
+
opaque bytes, so at download time there is nothing left to filter — and because
|
|
209
|
+
``TokenReadOnlyMethods`` refuses an agent token on POST, the archive was always
|
|
210
|
+
built *for a human*, so filtering at build time cannot see the eventual agent
|
|
211
|
+
reader either. The sync seed *could* be narrowed at request time, but a seed is
|
|
212
|
+
a re-import artifact: handing back something shaped like the program that
|
|
213
|
+
quietly is not the program is worse than refusing. So the lever is
|
|
214
|
+
serve-or-refuse, and the policy setting chooses which.
|
|
215
|
+
|
|
216
|
+
Returns ``False`` for the ``allow`` policy, and for a program with no opted-out
|
|
217
|
+
member project — the common case, answered with one ``EXISTS`` query.
|
|
218
|
+
"""
|
|
219
|
+
if program_export_policy() == PROGRAM_EXPORT_ALLOW:
|
|
220
|
+
return False
|
|
221
|
+
|
|
222
|
+
from trueppm_api.apps.projects.models import Project
|
|
223
|
+
|
|
224
|
+
# Evaluated against the program's CURRENT member projects, not against a
|
|
225
|
+
# manifest of what the archive actually contains — ``ProgramExportJob`` records
|
|
226
|
+
# no such manifest. That makes this very slightly over-inclusive (a project that
|
|
227
|
+
# opted out after the bundle was built still withholds it) and never
|
|
228
|
+
# under-inclusive, which is the correct direction for a consent control.
|
|
229
|
+
return Project.objects.filter(
|
|
230
|
+
program_id=program.pk, pk__in=mcp_opted_out_project_ids()
|
|
231
|
+
).exists()
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def mcp_reads_globally_disabled(*, workspace: Workspace | None = None) -> bool:
|
|
235
|
+
"""True when a scope above every project denies — instance or workspace.
|
|
236
|
+
|
|
237
|
+
Lets the enforcement point answer "deny everything" without materializing a
|
|
238
|
+
project-id set.
|
|
239
|
+
"""
|
|
240
|
+
from trueppm_api.apps.workspace.models import Workspace
|
|
241
|
+
|
|
242
|
+
if not _instance_enabled():
|
|
243
|
+
return True
|
|
244
|
+
if workspace is None:
|
|
245
|
+
workspace = Workspace.load()
|
|
246
|
+
return not workspace.mcp_enabled
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def mcp_excluded_project_ids(request: Request) -> QuerySet[Any] | None:
|
|
250
|
+
"""Project ids this request must NOT read, or ``None`` when nothing is withheld.
|
|
251
|
+
|
|
252
|
+
The exclusion-shaped companion to :func:`mcp_visible_project_ids`, for the
|
|
253
|
+
program-level services (``compute_program_schedule``,
|
|
254
|
+
``compute_program_rollup``) whose natural seam is "drop these projects" rather
|
|
255
|
+
than "keep these". Returns ``None`` for a human caller and for the common case
|
|
256
|
+
where no project on the instance has opted out, so those services keep their
|
|
257
|
+
existing query plan untouched on every normal request.
|
|
258
|
+
|
|
259
|
+
A workspace/instance-level denial is not expanded here: those are caught by
|
|
260
|
+
:class:`~trueppm_api.apps.access.permissions.McpProjectEnabled`, which 403s the
|
|
261
|
+
request before any service runs.
|
|
262
|
+
"""
|
|
263
|
+
from trueppm_api.apps.projects.models import is_agent_token
|
|
264
|
+
|
|
265
|
+
if not is_agent_token(getattr(request, "auth", None)):
|
|
266
|
+
return None # Human JWT/Session or the owner's own full-access PAT (#2877).
|
|
267
|
+
|
|
268
|
+
opted_out = mcp_opted_out_project_ids()
|
|
269
|
+
if not opted_out.exists():
|
|
270
|
+
return None
|
|
271
|
+
return opted_out
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def mcp_visible_project_ids(request: Request) -> QuerySet[Any] | None:
|
|
275
|
+
"""Project ids this request may read, or ``None`` when no filtering is needed.
|
|
276
|
+
|
|
277
|
+
The primitive the hand-built aggregate views (``/me/search``, ``/me/work/``,
|
|
278
|
+
workspace assets) and the program-level rollups intersect against. Returns:
|
|
279
|
+
|
|
280
|
+
* ``None`` — the caller is not an agent token (a human, or a ``legacy:full``
|
|
281
|
+
personal access token, which is a person's own credential and not subject to
|
|
282
|
+
agent consent — #2877), or no project on the instance has opted out. Callers
|
|
283
|
+
skip filtering entirely; this is the case on virtually every instance, so the
|
|
284
|
+
common path costs one cheap ``EXISTS``.
|
|
285
|
+
* an empty queryset — a scope above every project denies (instance or
|
|
286
|
+
workspace off), so nothing is visible.
|
|
287
|
+
* a values-list queryset of readable project ids otherwise.
|
|
288
|
+
|
|
289
|
+
why a *positive* id set rather than an exclusion list: the aggregate views
|
|
290
|
+
already join through several project relations, and an ``__in`` against the
|
|
291
|
+
allowed set composes with their existing membership filters without needing
|
|
292
|
+
each call site to reason about NULL semantics.
|
|
293
|
+
"""
|
|
294
|
+
from trueppm_api.apps.projects.models import Project, is_agent_token
|
|
295
|
+
|
|
296
|
+
if not is_agent_token(getattr(request, "auth", None)):
|
|
297
|
+
return None # Human JWT/Session or the owner's own full-access PAT (#2877).
|
|
298
|
+
|
|
299
|
+
if mcp_reads_globally_disabled():
|
|
300
|
+
return Project.objects.none().values_list("pk", flat=True)
|
|
301
|
+
|
|
302
|
+
opted_out = mcp_opted_out_project_ids()
|
|
303
|
+
if not opted_out.exists():
|
|
304
|
+
return None # Fast path: nothing opted out anywhere on the instance.
|
|
305
|
+
return Project.objects.exclude(pk__in=opted_out).values_list("pk", flat=True)
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"""Workspace → Program → Project methodology resolution (ADR-0107, issue 955).
|
|
2
|
+
|
|
3
|
+
The planning *methodology* (``AGILE`` / ``WATERFALL`` / ``HYBRID``) is the
|
|
4
|
+
"experience preset" that hides one workflow's chrome from the other (ADR-0041 —
|
|
5
|
+
the API surface and CPM substrate are unchanged; only tab visibility shifts). It
|
|
6
|
+
is set at three scopes; the *effective* methodology a project displays is resolved
|
|
7
|
+
here, computed-on-read (ADR-0108) — there is no stored/denormalized effective
|
|
8
|
+
column to keep in sync. Clients (web, mobile, MCP) read the serializer's
|
|
9
|
+
``effective_methodology``; they never re-implement this precedence.
|
|
10
|
+
|
|
11
|
+
**Crucial difference from iteration_label / sharing inheritance:** methodology is
|
|
12
|
+
NOT-NULL on every scope (Workspace/Program/Project each always carry a concrete
|
|
13
|
+
value — there is no null "inherit" sentinel). So inheritance is **policy-driven**,
|
|
14
|
+
not override-presence driven. The single switch is the workspace's
|
|
15
|
+
``methodology_override_policy``:
|
|
16
|
+
|
|
17
|
+
- ``SUGGEST`` (default) → each scope's own ``methodology`` is honored; the
|
|
18
|
+
workspace default merely pre-fills new projects. Precedence:
|
|
19
|
+
project.methodology → program.methodology → workspace.methodology.
|
|
20
|
+
- ``INHERIT`` → the workspace default wins for every scope; the per-scope picker
|
|
21
|
+
is read-only ("Inherited from workspace").
|
|
22
|
+
- ``ENFORCE`` → the workspace default is mandatory and the per-scope override is
|
|
23
|
+
blocked. ``ENFORCE`` is an Enterprise capability (trueppm-enterprise#144); OSS
|
|
24
|
+
ships the neutral hook below and registers no provider, so ``ENFORCE`` degrades
|
|
25
|
+
to ``SUGGEST`` (no lock — the per-scope override wins and the methodology PATCH
|
|
26
|
+
is allowed).
|
|
27
|
+
|
|
28
|
+
OSS never imports enterprise. trueppm-enterprise registers a provider in its
|
|
29
|
+
``AppConfig.ready()`` — the integrations-registry idiom (ADR-0029/0049), mirroring
|
|
30
|
+
``iteration_label.register_terminology_enforcement_provider`` and
|
|
31
|
+
``sharing_settings.register_sharing_enforcement_provider``.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from __future__ import annotations
|
|
35
|
+
|
|
36
|
+
from collections.abc import Callable
|
|
37
|
+
from typing import TYPE_CHECKING
|
|
38
|
+
|
|
39
|
+
from trueppm_api.apps.projects.models import Methodology
|
|
40
|
+
from trueppm_api.core.extension_providers import guard_single_provider
|
|
41
|
+
|
|
42
|
+
if TYPE_CHECKING:
|
|
43
|
+
from trueppm_api.apps.projects.models import Program, Project
|
|
44
|
+
from trueppm_api.apps.workspace.models import Workspace
|
|
45
|
+
|
|
46
|
+
#: System backstop when nothing up the chain sets a methodology. Mirrors the
|
|
47
|
+
#: model defaults (HYBRID shows every tab — the safe, lossless default).
|
|
48
|
+
DEFAULT_METHODOLOGY = Methodology.HYBRID
|
|
49
|
+
|
|
50
|
+
# Enterprise registers a zero-arg predicate that returns True when methodology
|
|
51
|
+
# enforcement is licensed/active. OSS leaves it None → enforcement inactive, so
|
|
52
|
+
# a workspace ENFORCE policy never locks downstream overrides in the community
|
|
53
|
+
# edition.
|
|
54
|
+
_ENFORCEMENT_PROVIDER: Callable[[], bool] | None = None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def register_methodology_enforcement_provider(provider: Callable[[], bool] | None) -> None:
|
|
58
|
+
"""Register (or clear) the methodology-enforcement provider. Enterprise calls this."""
|
|
59
|
+
global _ENFORCEMENT_PROVIDER
|
|
60
|
+
_ENFORCEMENT_PROVIDER = guard_single_provider(
|
|
61
|
+
_ENFORCEMENT_PROVIDER, provider, "methodology enforcement"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def methodology_enforcement_active() -> bool:
|
|
66
|
+
"""True only when an enterprise provider is registered AND reports active.
|
|
67
|
+
|
|
68
|
+
OSS has no provider → always False, so a workspace ``ENFORCE`` policy never
|
|
69
|
+
locks downstream methodology overrides in the community edition.
|
|
70
|
+
"""
|
|
71
|
+
return _ENFORCEMENT_PROVIDER is not None and bool(_ENFORCEMENT_PROVIDER())
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def methodology_override_locked(workspace: Workspace) -> bool:
|
|
75
|
+
"""Whether per-scope methodology overrides are locked to the workspace default.
|
|
76
|
+
|
|
77
|
+
True under ``INHERIT`` (always — the affordance is hidden by design) or under
|
|
78
|
+
``ENFORCE`` *with* an active enterprise provider. ``SUGGEST`` (and OSS
|
|
79
|
+
``ENFORCE`` with no provider) returns False, so the per-scope override is
|
|
80
|
+
honored and editable. This is the single predicate the serializer and the
|
|
81
|
+
write-permission gate both consult, so resolution and enforcement can never
|
|
82
|
+
drift apart.
|
|
83
|
+
"""
|
|
84
|
+
from trueppm_api.apps.workspace.models import TermOverridePolicy
|
|
85
|
+
|
|
86
|
+
policy = workspace.methodology_override_policy
|
|
87
|
+
if policy == TermOverridePolicy.INHERIT:
|
|
88
|
+
return True
|
|
89
|
+
if policy == TermOverridePolicy.ENFORCE:
|
|
90
|
+
return methodology_enforcement_active()
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def resolve_effective_methodology(
|
|
95
|
+
obj: Program | Project, *, workspace: Workspace | None = None
|
|
96
|
+
) -> str:
|
|
97
|
+
"""Resolve the effective methodology a program or project should display.
|
|
98
|
+
|
|
99
|
+
``workspace`` may be passed to avoid re-loading the singleton per object when
|
|
100
|
+
resolving a list (the serializer caches it once); otherwise it is loaded here.
|
|
101
|
+
"""
|
|
102
|
+
from trueppm_api.apps.workspace.models import Workspace
|
|
103
|
+
|
|
104
|
+
if workspace is None:
|
|
105
|
+
workspace = Workspace.load()
|
|
106
|
+
|
|
107
|
+
if methodology_override_locked(workspace):
|
|
108
|
+
# INHERIT / active ENFORCE: the workspace default wins regardless of any
|
|
109
|
+
# lower-scope value.
|
|
110
|
+
return workspace.methodology or DEFAULT_METHODOLOGY
|
|
111
|
+
|
|
112
|
+
# SUGGEST (or OSS ENFORCE with no provider): each scope's own value wins, then
|
|
113
|
+
# the program tier, then the workspace default. Every value is non-null, so
|
|
114
|
+
# this short-circuits at the object itself in the common case.
|
|
115
|
+
from trueppm_api.apps.projects.models import Program
|
|
116
|
+
|
|
117
|
+
own = obj.methodology
|
|
118
|
+
if own:
|
|
119
|
+
return own
|
|
120
|
+
program = obj.program if (not isinstance(obj, Program) and obj.program_id) else None
|
|
121
|
+
program_value = program.methodology if program else None
|
|
122
|
+
return program_value or workspace.methodology or DEFAULT_METHODOLOGY
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def resolve_new_project_methodology(
|
|
126
|
+
program: Program | None, *, workspace: Workspace | None = None
|
|
127
|
+
) -> str:
|
|
128
|
+
"""Methodology a *new* project starts with when the caller did not choose one.
|
|
129
|
+
|
|
130
|
+
The resolver above is computed-on-read, but a project's ``methodology`` column
|
|
131
|
+
is NOT-NULL with a HYBRID model default — so a project created without an
|
|
132
|
+
explicit value is *stored* as HYBRID and, under ``SUGGEST``, stays HYBRID at
|
|
133
|
+
read time no matter what its program or workspace says (the project's own
|
|
134
|
+
value wins the chain). The "workspace default pre-fills new projects" and
|
|
135
|
+
"new projects in this program start with its methodology" promises are
|
|
136
|
+
therefore only true if whoever creates the project seeds the stored value.
|
|
137
|
+
The interactive New-project sheet does that client-side
|
|
138
|
+
(``deriveStartSheetMethodology``: ``program.effective_methodology`` if a
|
|
139
|
+
program is selected, else the workspace default); this is the server-side
|
|
140
|
+
equivalent for every other door a project enters through — the MS Project
|
|
141
|
+
create-from-import endpoint today — so the delivery model a project lands
|
|
142
|
+
with cannot depend on which door it came through (#3432).
|
|
143
|
+
|
|
144
|
+
Under an active lock the answer is the workspace default either way, which is
|
|
145
|
+
also exactly what the effective resolver will report for the new project.
|
|
146
|
+
"""
|
|
147
|
+
from trueppm_api.apps.workspace.models import Workspace
|
|
148
|
+
|
|
149
|
+
if workspace is None:
|
|
150
|
+
workspace = Workspace.load()
|
|
151
|
+
if program is not None:
|
|
152
|
+
return resolve_effective_methodology(program, workspace=workspace)
|
|
153
|
+
return workspace.methodology or DEFAULT_METHODOLOGY
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def resolve_inherited_methodology(
|
|
157
|
+
obj: Program | Project, *, workspace: Workspace | None = None
|
|
158
|
+
) -> str:
|
|
159
|
+
"""Methodology ``obj`` would display if its own override were ignored.
|
|
160
|
+
|
|
161
|
+
Drives the settings "Inherited from workspace (X)" affordance — it skips
|
|
162
|
+
``obj``'s own value and resolves from the parent scope up. A Program's parent
|
|
163
|
+
is the workspace; a Project's parent is its program (whose own value resolves
|
|
164
|
+
up to the workspace) or, standalone, the workspace. Under an active lock the
|
|
165
|
+
inherited value is the (mandatory) workspace default.
|
|
166
|
+
"""
|
|
167
|
+
from trueppm_api.apps.projects.models import Program
|
|
168
|
+
from trueppm_api.apps.workspace.models import Workspace
|
|
169
|
+
|
|
170
|
+
if workspace is None:
|
|
171
|
+
workspace = Workspace.load()
|
|
172
|
+
|
|
173
|
+
if methodology_override_locked(workspace):
|
|
174
|
+
return workspace.methodology or DEFAULT_METHODOLOGY
|
|
175
|
+
|
|
176
|
+
program = obj.program if (not isinstance(obj, Program) and obj.program_id) else None
|
|
177
|
+
program_value = program.methodology if program else None
|
|
178
|
+
return program_value or workspace.methodology or DEFAULT_METHODOLOGY
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Shared helper for the repo's ``CREATE INDEX CONCURRENTLY`` migrations (#4357).
|
|
2
|
+
|
|
3
|
+
``CREATE INDEX CONCURRENTLY`` does not roll back on failure the way an ordinary
|
|
4
|
+
DDL statement does: if the build is interrupted partway through, PostgreSQL
|
|
5
|
+
leaves the index present but marked ``INVALID`` (the planner ignores it, but
|
|
6
|
+
every write still pays to maintain it). Under `migrate_locked` at
|
|
7
|
+
``replicaCount >= 2`` (the shipped default), every pod's init container races
|
|
8
|
+
an advisory lock for the one migration run; if the lock-holding pod dies
|
|
9
|
+
mid-build (OOM, eviction, node drain), the next pod to win the lock reruns the
|
|
10
|
+
*same* migration. The `IF NOT EXISTS` guard on the `CREATE INDEX CONCURRENTLY`
|
|
11
|
+
sees an index of that name already present, skips it, and the migration is
|
|
12
|
+
recorded applied — so the index is permanently INVALID and unused by the
|
|
13
|
+
planner, with nothing surfacing an error.
|
|
14
|
+
|
|
15
|
+
This module extracts the repair as a function so a test can call it directly
|
|
16
|
+
without importing a migration module by name, which a squash would delete
|
|
17
|
+
(CLAUDE.md migration rule 3) — the same pattern `projects.backfill` uses for
|
|
18
|
+
0148's repair step.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
from django.db import migrations
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def repair_invalid_concurrent_index(connection: Any, index_name: str) -> None:
|
|
29
|
+
"""Drop ``index_name`` with ``DROP INDEX CONCURRENTLY`` if it is INVALID.
|
|
30
|
+
|
|
31
|
+
No-op when the index does not exist, or exists and is valid — safe to run
|
|
32
|
+
ahead of every ``CREATE INDEX CONCURRENTLY IF NOT EXISTS`` build of the same
|
|
33
|
+
name, on every migrate, not just a crash-recovery run.
|
|
34
|
+
|
|
35
|
+
``DROP INDEX CONCURRENTLY`` cannot run inside a transaction block or a
|
|
36
|
+
``DO`` block (PostgreSQL executes a ``DO`` body as a single implicit
|
|
37
|
+
transaction), so this takes a bare connection and issues the statement
|
|
38
|
+
directly over a cursor — the caller must be a ``RunPython`` operation in an
|
|
39
|
+
``atomic = False`` migration, matching the forward build it precedes.
|
|
40
|
+
"""
|
|
41
|
+
with connection.cursor() as cursor:
|
|
42
|
+
cursor.execute(
|
|
43
|
+
"SELECT 1 FROM pg_class c "
|
|
44
|
+
"JOIN pg_index i ON i.indexrelid = c.oid "
|
|
45
|
+
"WHERE c.relname = %s AND NOT i.indisvalid",
|
|
46
|
+
[index_name],
|
|
47
|
+
)
|
|
48
|
+
if cursor.fetchone() is not None:
|
|
49
|
+
# `index_name` is always a hardcoded module-level constant from the
|
|
50
|
+
# calling migration (see repair_invalid_concurrent_index_op below),
|
|
51
|
+
# never request- or user-derived input — DDL identifiers can't be
|
|
52
|
+
# bound as query parameters, so quoting is the only option anyway.
|
|
53
|
+
# nosemgrep: formatted-sql-query,sqlalchemy-execute-raw-query
|
|
54
|
+
cursor.execute(f'DROP INDEX CONCURRENTLY IF EXISTS "{index_name}";')
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def repair_invalid_concurrent_index_op(index_name: str) -> migrations.RunPython:
|
|
58
|
+
"""Build the ``RunPython`` migration operation wrapping the repair above.
|
|
59
|
+
|
|
60
|
+
Place this immediately before the existing
|
|
61
|
+
``CREATE INDEX CONCURRENTLY IF NOT EXISTS`` ``RunSQL`` operation for the
|
|
62
|
+
same index name. The reverse is a no-op: tearing an INVALID index back down
|
|
63
|
+
is not meaningful on a rollback, and the forward build that follows this
|
|
64
|
+
operation already makes the index valid again on the next `migrate`.
|
|
65
|
+
|
|
66
|
+
``atomic=False`` is passed explicitly (on top of the migration's own
|
|
67
|
+
``atomic = False``) so Django's executor never wraps this operation in an
|
|
68
|
+
implicit transaction on a backend where that would otherwise happen — see
|
|
69
|
+
``Migration.apply()``'s ``atomic_operation`` check.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def _forward(apps: Any, schema_editor: Any) -> None:
|
|
73
|
+
repair_invalid_concurrent_index(schema_editor.connection, index_name)
|
|
74
|
+
|
|
75
|
+
return migrations.RunPython(_forward, migrations.RunPython.noop, atomic=False)
|