trueppm-api 0.4.0b2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- trueppm_api/__init__.py +14 -0
- trueppm_api/apps/access/__init__.py +0 -0
- trueppm_api/apps/access/apps.py +19 -0
- trueppm_api/apps/access/groups.py +518 -0
- trueppm_api/apps/access/management/__init__.py +0 -0
- trueppm_api/apps/access/management/commands/__init__.py +0 -0
- trueppm_api/apps/access/management/commands/create_admin.py +164 -0
- trueppm_api/apps/access/management/commands/migrate_locked.py +96 -0
- trueppm_api/apps/access/management/commands/revoke_api_tokens.py +209 -0
- trueppm_api/apps/access/migrations/0001_initial.py +61 -0
- trueppm_api/apps/access/migrations/0001_squashed_0012_programmembership_role_title.py +167 -0
- trueppm_api/apps/access/migrations/0002_alter_projectmembership_id.py +29 -0
- trueppm_api/apps/access/migrations/0003_projectmembership_soft_delete.py +24 -0
- trueppm_api/apps/access/migrations/0004_alter_projectmembership_role.py +34 -0
- trueppm_api/apps/access/migrations/0005_program_entity_and_membership.py +76 -0
- trueppm_api/apps/access/migrations/0006_role_ordinal_spacing.py +113 -0
- trueppm_api/apps/access/migrations/0007_projectmembership_joined_at_and_more.py +35 -0
- trueppm_api/apps/access/migrations/0008_projectmembership_source_group.py +25 -0
- trueppm_api/apps/access/migrations/0009_projectmembership_pm_proj_serverver_idx.py +20 -0
- trueppm_api/apps/access/migrations/0010_programmembership_joined_at_and_more.py +38 -0
- trueppm_api/apps/access/migrations/0011_alter_programmembership_unique_together_and_more.py +36 -0
- trueppm_api/apps/access/migrations/0012_programmembership_role_title.py +17 -0
- trueppm_api/apps/access/migrations/0013_userdefinedmentiongroup.py +82 -0
- trueppm_api/apps/access/migrations/0014_programmembership_progm_serverver_idx.py +19 -0
- trueppm_api/apps/access/migrations/0015_programuserdefinedmentiongroup.py +86 -0
- trueppm_api/apps/access/migrations/0016_externalstakeholder.py +67 -0
- trueppm_api/apps/access/migrations/0017_viewer_ordinal_one.py +132 -0
- trueppm_api/apps/access/migrations/0018_programmembership_sync_seq_and_more.py +52 -0
- trueppm_api/apps/access/migrations/0019_programmembership_reinstated_at_and_more.py +22 -0
- trueppm_api/apps/access/migrations/__init__.py +0 -0
- trueppm_api/apps/access/models.py +588 -0
- trueppm_api/apps/access/permissions.py +2837 -0
- trueppm_api/apps/access/serializers.py +946 -0
- trueppm_api/apps/access/services.py +667 -0
- trueppm_api/apps/access/signals.py +103 -0
- trueppm_api/apps/access/tasks.py +50 -0
- trueppm_api/apps/access/throttles.py +95 -0
- trueppm_api/apps/access/urls.py +191 -0
- trueppm_api/apps/access/views.py +1931 -0
- trueppm_api/apps/agents/__init__.py +6 -0
- trueppm_api/apps/agents/apps.py +11 -0
- trueppm_api/apps/agents/canonical.py +73 -0
- trueppm_api/apps/agents/deferred.py +108 -0
- trueppm_api/apps/agents/management/__init__.py +0 -0
- trueppm_api/apps/agents/management/commands/__init__.py +0 -0
- trueppm_api/apps/agents/management/commands/audit_prune.py +137 -0
- trueppm_api/apps/agents/management/commands/audit_verify.py +101 -0
- trueppm_api/apps/agents/middleware.py +44 -0
- trueppm_api/apps/agents/migrations/0001_initial.py +231 -0
- trueppm_api/apps/agents/migrations/0002_agentactioncheckpoint.py +71 -0
- trueppm_api/apps/agents/migrations/0003_agentactionrefusaldetail.py +58 -0
- trueppm_api/apps/agents/migrations/__init__.py +0 -0
- trueppm_api/apps/agents/models.py +371 -0
- trueppm_api/apps/agents/refusal.py +103 -0
- trueppm_api/apps/agents/serializers.py +85 -0
- trueppm_api/apps/agents/services.py +397 -0
- trueppm_api/apps/agents/signals.py +30 -0
- trueppm_api/apps/agents/urls.py +15 -0
- trueppm_api/apps/agents/views.py +108 -0
- trueppm_api/apps/csvimport/__init__.py +12 -0
- trueppm_api/apps/csvimport/apps.py +11 -0
- trueppm_api/apps/csvimport/mapping.py +363 -0
- trueppm_api/apps/csvimport/migrations/0001_initial.py +74 -0
- trueppm_api/apps/csvimport/migrations/0002_schedule_outline_batch_operations.py +37 -0
- trueppm_api/apps/csvimport/migrations/0003_csvimportrequest_date_order_and_more.py +26 -0
- trueppm_api/apps/csvimport/migrations/__init__.py +0 -0
- trueppm_api/apps/csvimport/models.py +111 -0
- trueppm_api/apps/csvimport/parser.py +1825 -0
- trueppm_api/apps/csvimport/services.py +48 -0
- trueppm_api/apps/csvimport/tasks.py +418 -0
- trueppm_api/apps/csvimport/template.py +37 -0
- trueppm_api/apps/csvimport/urls.py +50 -0
- trueppm_api/apps/csvimport/views.py +793 -0
- trueppm_api/apps/history/__init__.py +0 -0
- trueppm_api/apps/history/apps.py +12 -0
- trueppm_api/apps/history/changelog.py +359 -0
- trueppm_api/apps/history/diff_policy.py +177 -0
- trueppm_api/apps/history/serializers.py +104 -0
- trueppm_api/apps/history/signals.py +82 -0
- trueppm_api/apps/history/tasks.py +105 -0
- trueppm_api/apps/history/urls.py +38 -0
- trueppm_api/apps/history/views.py +443 -0
- trueppm_api/apps/idempotency/__init__.py +0 -0
- trueppm_api/apps/idempotency/apps.py +11 -0
- trueppm_api/apps/idempotency/migrations/0001_initial.py +71 -0
- trueppm_api/apps/idempotency/migrations/__init__.py +0 -0
- trueppm_api/apps/idempotency/mixins.py +226 -0
- trueppm_api/apps/idempotency/models.py +75 -0
- trueppm_api/apps/idempotency/tasks.py +61 -0
- trueppm_api/apps/integrations/__init__.py +13 -0
- trueppm_api/apps/integrations/apps.py +59 -0
- trueppm_api/apps/integrations/connections.py +806 -0
- trueppm_api/apps/integrations/encryption.py +92 -0
- trueppm_api/apps/integrations/external_sources.py +837 -0
- trueppm_api/apps/integrations/git_automation_services.py +231 -0
- trueppm_api/apps/integrations/git_webhook_auth.py +243 -0
- trueppm_api/apps/integrations/http.py +403 -0
- trueppm_api/apps/integrations/me_work.py +218 -0
- trueppm_api/apps/integrations/migrations/0001_initial.py +64 -0
- trueppm_api/apps/integrations/migrations/0001_squashed_0006_tasklink_description_tasklink_preview_type_and_more.py +187 -0
- trueppm_api/apps/integrations/migrations/0002_tasklink.py +67 -0
- trueppm_api/apps/integrations/migrations/0003_tasklink_tasklink_serverver_idx.py +17 -0
- trueppm_api/apps/integrations/migrations/0004_tasklink_custom_title_tasklink_labels.py +25 -0
- trueppm_api/apps/integrations/migrations/0005_boardautomation.py +56 -0
- trueppm_api/apps/integrations/migrations/0006_tasklink_description_tasklink_preview_type_and_more.py +40 -0
- trueppm_api/apps/integrations/migrations/0007_tasklink_created_at.py +23 -0
- trueppm_api/apps/integrations/migrations/0008_integrationcredential_config_externalworkitem.py +66 -0
- trueppm_api/apps/integrations/migrations/0009_externalsyncrequest_and_more.py +96 -0
- trueppm_api/apps/integrations/migrations/0010_externalworkitem_due_date.py +17 -0
- trueppm_api/apps/integrations/migrations/0011_tasklink_sync_seq_tasklink_tasklink_syncseq_idx.py +22 -0
- trueppm_api/apps/integrations/migrations/0012_boardautomation_last_delivery_at_and_more.py +42 -0
- trueppm_api/apps/integrations/migrations/0013_externalsyncrequest_attempt_count_and_more.py +26 -0
- trueppm_api/apps/integrations/migrations/__init__.py +0 -0
- trueppm_api/apps/integrations/models.py +622 -0
- trueppm_api/apps/integrations/notification_channels.py +57 -0
- trueppm_api/apps/integrations/opengraph.py +229 -0
- trueppm_api/apps/integrations/outgoing.py +201 -0
- trueppm_api/apps/integrations/providers.py +519 -0
- trueppm_api/apps/integrations/registry.py +345 -0
- trueppm_api/apps/integrations/serializers.py +453 -0
- trueppm_api/apps/integrations/services.py +171 -0
- trueppm_api/apps/integrations/tasks.py +731 -0
- trueppm_api/apps/integrations/throttles.py +165 -0
- trueppm_api/apps/integrations/urls.py +77 -0
- trueppm_api/apps/integrations/views.py +1106 -0
- trueppm_api/apps/jiraimport/__init__.py +8 -0
- trueppm_api/apps/jiraimport/apps.py +11 -0
- trueppm_api/apps/jiraimport/migrations/0001_initial.py +72 -0
- trueppm_api/apps/jiraimport/migrations/__init__.py +0 -0
- trueppm_api/apps/jiraimport/models.py +70 -0
- trueppm_api/apps/jiraimport/parser.py +350 -0
- trueppm_api/apps/jiraimport/services.py +46 -0
- trueppm_api/apps/jiraimport/tasks.py +277 -0
- trueppm_api/apps/jiraimport/urls.py +15 -0
- trueppm_api/apps/jiraimport/views.py +153 -0
- trueppm_api/apps/msproject/__init__.py +0 -0
- trueppm_api/apps/msproject/apps.py +11 -0
- trueppm_api/apps/msproject/dataclasses.py +153 -0
- trueppm_api/apps/msproject/exporter.py +500 -0
- trueppm_api/apps/msproject/extended_attributes.py +77 -0
- trueppm_api/apps/msproject/importer.py +1095 -0
- trueppm_api/apps/msproject/migrations/0001_import_request.py +77 -0
- trueppm_api/apps/msproject/migrations/0001_squashed_0002_importrequest_creates_project.py +80 -0
- trueppm_api/apps/msproject/migrations/0002_importrequest_creates_project.py +15 -0
- trueppm_api/apps/msproject/migrations/__init__.py +0 -0
- trueppm_api/apps/msproject/models.py +81 -0
- trueppm_api/apps/msproject/parser.py +1231 -0
- trueppm_api/apps/msproject/serializers.py +136 -0
- trueppm_api/apps/msproject/services.py +58 -0
- trueppm_api/apps/msproject/tasks.py +436 -0
- trueppm_api/apps/msproject/urls.py +39 -0
- trueppm_api/apps/msproject/views.py +531 -0
- trueppm_api/apps/notifications/__init__.py +7 -0
- trueppm_api/apps/notifications/apps.py +15 -0
- trueppm_api/apps/notifications/backfill.py +79 -0
- trueppm_api/apps/notifications/categories.py +112 -0
- trueppm_api/apps/notifications/delivery_limits.py +258 -0
- trueppm_api/apps/notifications/digests.py +447 -0
- trueppm_api/apps/notifications/email_backend.py +330 -0
- trueppm_api/apps/notifications/email_health.py +128 -0
- trueppm_api/apps/notifications/migrations/0001_initial.py +207 -0
- trueppm_api/apps/notifications/migrations/0001_squashed_0006_notification_task.py +294 -0
- trueppm_api/apps/notifications/migrations/0002_project_notification_preference.py +67 -0
- trueppm_api/apps/notifications/migrations/0003_projectnotificationpreference_paused.py +17 -0
- trueppm_api/apps/notifications/migrations/0004_clean_unknown_matrix_keys.py +30 -0
- trueppm_api/apps/notifications/migrations/0005_notification_body_notification_event_type_and_more.py +27 -0
- trueppm_api/apps/notifications/migrations/0006_notification_task.py +25 -0
- trueppm_api/apps/notifications/migrations/0007_workspaceemailsettings.py +80 -0
- trueppm_api/apps/notifications/migrations/0008_notification_snoozed_until.py +17 -0
- trueppm_api/apps/notifications/migrations/0009_usernotificationsettings.py +43 -0
- trueppm_api/apps/notifications/migrations/0010_projectnotificationpreference_schema_version.py +20 -0
- trueppm_api/apps/notifications/migrations/0011_usernotificationsettings_digest_hour_and_more.py +96 -0
- trueppm_api/apps/notifications/migrations/__init__.py +0 -0
- trueppm_api/apps/notifications/models.py +1197 -0
- trueppm_api/apps/notifications/receivers.py +155 -0
- trueppm_api/apps/notifications/schema_migrations.py +56 -0
- trueppm_api/apps/notifications/serializers.py +961 -0
- trueppm_api/apps/notifications/services.py +1451 -0
- trueppm_api/apps/notifications/tasks.py +711 -0
- trueppm_api/apps/notifications/throttles.py +98 -0
- trueppm_api/apps/notifications/urls.py +97 -0
- trueppm_api/apps/notifications/views.py +904 -0
- trueppm_api/apps/observability/__init__.py +0 -0
- trueppm_api/apps/observability/apps.py +31 -0
- trueppm_api/apps/observability/logging.py +213 -0
- trueppm_api/apps/observability/migrations/0001_initial.py +35 -0
- trueppm_api/apps/observability/migrations/0001_squashed_0003_alter_purgerun_trigger.py +154 -0
- trueppm_api/apps/observability/migrations/0002_purgerun_retentionpolicy_retentionschedule.py +131 -0
- trueppm_api/apps/observability/migrations/0003_alter_purgerun_trigger.py +21 -0
- trueppm_api/apps/observability/migrations/0004_alter_retentionpolicy_key.py +28 -0
- trueppm_api/apps/observability/migrations/__init__.py +0 -0
- trueppm_api/apps/observability/models.py +141 -0
- trueppm_api/apps/observability/otel/__init__.py +45 -0
- trueppm_api/apps/observability/otel/attributes.py +247 -0
- trueppm_api/apps/observability/otel/export_health.py +533 -0
- trueppm_api/apps/observability/otel/instrumentation.py +351 -0
- trueppm_api/apps/observability/otel/metrics.py +551 -0
- trueppm_api/apps/observability/otel/provider.py +511 -0
- trueppm_api/apps/observability/otel/request_attributes.py +204 -0
- trueppm_api/apps/observability/purge_registry.py +124 -0
- trueppm_api/apps/observability/retention.py +139 -0
- trueppm_api/apps/observability/selectors.py +969 -0
- trueppm_api/apps/observability/serializers.py +142 -0
- trueppm_api/apps/observability/services.py +407 -0
- trueppm_api/apps/observability/tasks.py +266 -0
- trueppm_api/apps/observability/urls.py +27 -0
- trueppm_api/apps/observability/views.py +523 -0
- trueppm_api/apps/profiles/__init__.py +0 -0
- trueppm_api/apps/profiles/apps.py +15 -0
- trueppm_api/apps/profiles/constants.py +54 -0
- trueppm_api/apps/profiles/migrations/0001_initial.py +55 -0
- trueppm_api/apps/profiles/migrations/0001_squashed_0004_userprofile_role_context.py +125 -0
- trueppm_api/apps/profiles/migrations/0002_userprofile_hidden_views.py +17 -0
- trueppm_api/apps/profiles/migrations/0003_projectvisit.py +63 -0
- trueppm_api/apps/profiles/migrations/0004_userprofile_role_context.py +26 -0
- trueppm_api/apps/profiles/migrations/0005_userprofile_schedule_in_deliver.py +20 -0
- trueppm_api/apps/profiles/migrations/0006_userprofile_date_format_userprofile_timezone.py +36 -0
- trueppm_api/apps/profiles/migrations/0007_userpin.py +91 -0
- trueppm_api/apps/profiles/migrations/0008_remove_userprofile_schedule_in_deliver.py +71 -0
- trueppm_api/apps/profiles/migrations/0009_auth_user_email_upper_idx.py +81 -0
- trueppm_api/apps/profiles/migrations/__init__.py +0 -0
- trueppm_api/apps/profiles/models.py +276 -0
- trueppm_api/apps/profiles/serializers.py +199 -0
- trueppm_api/apps/profiles/services.py +466 -0
- trueppm_api/apps/profiles/views.py +124 -0
- trueppm_api/apps/projects/__init__.py +0 -0
- trueppm_api/apps/projects/actual_date_rules.py +163 -0
- trueppm_api/apps/projects/amend.py +133 -0
- trueppm_api/apps/projects/apps.py +38 -0
- trueppm_api/apps/projects/asset_feed.py +459 -0
- trueppm_api/apps/projects/asset_views.py +317 -0
- trueppm_api/apps/projects/attachment_policy.py +278 -0
- trueppm_api/apps/projects/authentication.py +449 -0
- trueppm_api/apps/projects/backfill.py +287 -0
- trueppm_api/apps/projects/backlog_services.py +324 -0
- trueppm_api/apps/projects/backlog_views.py +302 -0
- trueppm_api/apps/projects/batch_operation_views.py +264 -0
- trueppm_api/apps/projects/blocker_services.py +510 -0
- trueppm_api/apps/projects/board_activity.py +462 -0
- trueppm_api/apps/projects/board_activity_views.py +186 -0
- trueppm_api/apps/projects/board_lanes.py +134 -0
- trueppm_api/apps/projects/bulk_settings.py +194 -0
- trueppm_api/apps/projects/bundled_templates.py +215 -0
- trueppm_api/apps/projects/calendar_settings.py +180 -0
- trueppm_api/apps/projects/ceremony_views.py +244 -0
- trueppm_api/apps/projects/commit_moment.py +204 -0
- trueppm_api/apps/projects/config_notice.py +1325 -0
- trueppm_api/apps/projects/custom_field_values.py +266 -0
- trueppm_api/apps/projects/decisions_services.py +93 -0
- trueppm_api/apps/projects/decisions_views.py +94 -0
- trueppm_api/apps/projects/estimation_scale.py +126 -0
- trueppm_api/apps/projects/export_bundle.py +353 -0
- trueppm_api/apps/projects/fixtures/seeds/atlas-platform-launch.json +4256 -0
- trueppm_api/apps/projects/fixtures/seeds/aurora-mobile-app.json +2000 -0
- trueppm_api/apps/projects/fixtures/seeds/bayside-civic-center.json +1825 -0
- trueppm_api/apps/projects/fixtures/seeds/ga-launch.json +1905 -0
- trueppm_api/apps/projects/fixtures/seeds/helios-crm-replacement.json +1409 -0
- trueppm_api/apps/projects/guardrail_policy_source.py +269 -0
- trueppm_api/apps/projects/inbound_sync.py +555 -0
- trueppm_api/apps/projects/iteration_label.py +85 -0
- trueppm_api/apps/projects/lifecycle.py +191 -0
- trueppm_api/apps/projects/management/__init__.py +0 -0
- trueppm_api/apps/projects/management/commands/__init__.py +0 -0
- trueppm_api/apps/projects/management/commands/backfill_in_progress_status.py +92 -0
- trueppm_api/apps/projects/management/commands/create_demo_share_link.py +368 -0
- trueppm_api/apps/projects/management/commands/export_program.py +50 -0
- trueppm_api/apps/projects/management/commands/import_seed.py +165 -0
- trueppm_api/apps/projects/management/commands/load_sample_project.py +247 -0
- trueppm_api/apps/projects/management/commands/seed_capacity.py +405 -0
- trueppm_api/apps/projects/management/commands/seed_integration_fixtures.py +178 -0
- trueppm_api/apps/projects/management/commands/shift_sample_dates.py +120 -0
- trueppm_api/apps/projects/mcp_settings.py +305 -0
- trueppm_api/apps/projects/methodology.py +178 -0
- trueppm_api/apps/projects/migrations/0001_initial.py +208 -0
- trueppm_api/apps/projects/migrations/0001_squashed_0094_project_decisions_policy.py +5595 -0
- trueppm_api/apps/projects/migrations/0002_alter_uuid_pk_serialize_false.py +57 -0
- trueppm_api/apps/projects/migrations/0003_rename_task_project_index.py +25 -0
- trueppm_api/apps/projects/migrations/0004_soft_delete_and_dependency_versioned.py +70 -0
- trueppm_api/apps/projects/migrations/0005_task_assignee.py +33 -0
- trueppm_api/apps/projects/migrations/0006_task_utilization_index.py +31 -0
- trueppm_api/apps/projects/migrations/0007_task_is_milestone.py +28 -0
- trueppm_api/apps/projects/migrations/0008_baseline.py +119 -0
- trueppm_api/apps/projects/migrations/0009_historicaldependency_historicalproject_and_more.py +210 -0
- trueppm_api/apps/projects/migrations/0010_risk.py +229 -0
- trueppm_api/apps/projects/migrations/0011_task_status.py +27 -0
- trueppm_api/apps/projects/migrations/0012_task_status.py +27 -0
- trueppm_api/apps/projects/migrations/0013_task_planned_start.py +24 -0
- trueppm_api/apps/projects/migrations/0014_short_hex_ids.py +74 -0
- trueppm_api/apps/projects/migrations/0015_backfill_short_ids.py +48 -0
- trueppm_api/apps/projects/migrations/0016_task_actual_dates.py +42 -0
- trueppm_api/apps/projects/migrations/0017_boardcolumnconfig.py +43 -0
- trueppm_api/apps/projects/migrations/0018_estimation_governance.py +62 -0
- trueppm_api/apps/projects/migrations/0019_backfill_wbs_paths.py +28 -0
- trueppm_api/apps/projects/migrations/0020_task_status_5col.py +65 -0
- trueppm_api/apps/projects/migrations/0021_boardsavedview.py +64 -0
- trueppm_api/apps/projects/migrations/0022_task_status_changed_at_priority_rank.py +35 -0
- trueppm_api/apps/projects/migrations/0023_risk_framework_fields.py +96 -0
- trueppm_api/apps/projects/migrations/0024_riskcomment.py +52 -0
- trueppm_api/apps/projects/migrations/0025_sprint.py +352 -0
- trueppm_api/apps/projects/migrations/0026_project_methodology.py +30 -0
- trueppm_api/apps/projects/migrations/0027_sprint_retro.py +89 -0
- trueppm_api/apps/projects/migrations/0028_normalize_notes_field.py +42 -0
- trueppm_api/apps/projects/migrations/0029_complete_implies_full_progress.py +38 -0
- trueppm_api/apps/projects/migrations/0030_review_implies_full_progress.py +37 -0
- trueppm_api/apps/projects/migrations/0031_task_remaining_points.py +22 -0
- trueppm_api/apps/projects/migrations/0032_task_is_subtask_and_sprint_scope_change.py +90 -0
- trueppm_api/apps/projects/migrations/0033_task_my_work_index.py +38 -0
- trueppm_api/apps/projects/migrations/0034_inbound_task_sync.py +313 -0
- trueppm_api/apps/projects/migrations/0035_retro_versioned_and_suggested_assignee.py +173 -0
- trueppm_api/apps/projects/migrations/0036_program_entity_and_membership.py +157 -0
- trueppm_api/apps/projects/migrations/0037_sprint_capacity_points.py +20 -0
- trueppm_api/apps/projects/migrations/0038_taskcomment_commentreaction_commentacknowledgement_and_more.py +233 -0
- trueppm_api/apps/projects/migrations/0039_apitoken_and_more.py +88 -0
- trueppm_api/apps/projects/migrations/0040_program_general_fields.py +119 -0
- trueppm_api/apps/projects/migrations/0041_ceremony_template_and_phase_gate_config.py +210 -0
- trueppm_api/apps/projects/migrations/0041_project_general_fields.py +106 -0
- trueppm_api/apps/projects/migrations/0042_merge_20260522_0410.py +12 -0
- trueppm_api/apps/projects/migrations/0042_program_rollup_config.py +95 -0
- trueppm_api/apps/projects/migrations/0042_workflow_color_and_custom_fields.py +94 -0
- trueppm_api/apps/projects/migrations/0043_merge_20260522_1118.py +10 -0
- trueppm_api/apps/projects/migrations/0044_merge_20260522_rollup.py +10 -0
- trueppm_api/apps/projects/migrations/0044_project_archive_program_close.py +119 -0
- trueppm_api/apps/projects/migrations/0045_program_risk_policy.py +78 -0
- trueppm_api/apps/projects/migrations/0046_merge_20260523_2248.py +12 -0
- trueppm_api/apps/projects/migrations/0047_apitokenauditentry_program_and_more.py +55 -0
- trueppm_api/apps/projects/migrations/0048_historicalprogram_color_program_color.py +32 -0
- trueppm_api/apps/projects/migrations/0049_backlog_item.py +112 -0
- trueppm_api/apps/projects/migrations/0050_backlog_item_trgm_search.py +26 -0
- trueppm_api/apps/projects/migrations/0051_alter_calendar_working_days_alter_dependency_lag_and_more.py +144 -0
- trueppm_api/apps/projects/migrations/0052_recurring_tasks.py +278 -0
- trueppm_api/apps/projects/migrations/0053_dependency_dep_pred_serverver_idx_and_more.py +56 -0
- trueppm_api/apps/projects/migrations/0054_sprintscopechange_goal_impact_and_more.py +117 -0
- trueppm_api/apps/projects/migrations/0055_historicaltask_sprint_pending_and_more.py +51 -0
- trueppm_api/apps/projects/migrations/0056_historicalproject_prioritization_model_and_more.py +276 -0
- trueppm_api/apps/projects/migrations/0057_historicalsprint_binding_committed_snapshot_and_more.py +58 -0
- trueppm_api/apps/projects/migrations/0058_historicaltask_delivery_mode_and_more.py +72 -0
- trueppm_api/apps/projects/migrations/0059_forecastsnapshot.py +80 -0
- trueppm_api/apps/projects/migrations/0060_historicalprojectsignalprivacypolicy_and_more.py +90 -0
- trueppm_api/apps/projects/migrations/0061_baselinetask_story_points.py +17 -0
- trueppm_api/apps/projects/migrations/0062_project_is_sample.py +22 -0
- trueppm_api/apps/projects/migrations/0063_historicalsprint_wip_limit_sprint_wip_limit.py +22 -0
- trueppm_api/apps/projects/migrations/0064_sprinttaskoutcome.py +97 -0
- trueppm_api/apps/projects/migrations/0065_historicalsprint_goal_outcome_sprint_goal_outcome.py +32 -0
- trueppm_api/apps/projects/migrations/0066_historicalproject_lead_project_lead.py +38 -0
- trueppm_api/apps/projects/migrations/0067_sprint_exclude_from_velocity.py +22 -0
- trueppm_api/apps/projects/migrations/0068_project_recalculated_at.py +23 -0
- trueppm_api/apps/projects/migrations/0069_iteration_label.py +22 -0
- trueppm_api/apps/projects/migrations/0070_historicalprogram_iteration_label_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0071_backfill_iteration_label_inherit.py +35 -0
- trueppm_api/apps/projects/migrations/0072_pulseresponse_retroboarditem.py +129 -0
- trueppm_api/apps/projects/migrations/0073_sprinttaskoutcome_demo_ready.py +17 -0
- trueppm_api/apps/projects/migrations/0074_historicaltask_blocked_reason_task_blocked_reason.py +30 -0
- trueppm_api/apps/projects/migrations/0075_risk_decimal_short_id.py +42 -0
- trueppm_api/apps/projects/migrations/0076_sprinttaskoutcome_review_polish.py +45 -0
- trueppm_api/apps/projects/migrations/0077_task_blocker_fields.py +111 -0
- trueppm_api/apps/projects/migrations/0078_alter_risktask_unique_together_and_more.py +22 -0
- trueppm_api/apps/projects/migrations/0079_historicalproject_status_date_project_status_date.py +22 -0
- trueppm_api/apps/projects/migrations/0080_alter_historicaltask_type_alter_task_type.py +46 -0
- trueppm_api/apps/projects/migrations/0081_historicalprogram_allow_guests_and_more.py +52 -0
- trueppm_api/apps/projects/migrations/0082_project_last_sync_version.py +90 -0
- trueppm_api/apps/projects/migrations/0083_historicalprogram_mc_history_attribution_audience_and_more.py +108 -0
- trueppm_api/apps/projects/migrations/0084_tasknote.py +71 -0
- trueppm_api/apps/projects/migrations/0085_sprint_ix_sprint_velocity.py +33 -0
- trueppm_api/apps/projects/migrations/0086_board_card_search_trgm.py +33 -0
- trueppm_api/apps/projects/migrations/0087_historicalprogram_task_duration_change_percent_policy_and_more.py +155 -0
- trueppm_api/apps/projects/migrations/0088_historicalprogram_allowed_attachment_types_and_more.py +61 -0
- trueppm_api/apps/projects/migrations/0089_signalceilingraiseproposal_signalceilingraisevote_and_more.py +157 -0
- trueppm_api/apps/projects/migrations/0090_historicaltask_proj_histdate_index.py +44 -0
- trueppm_api/apps/projects/migrations/0091_project_board_cadence.py +39 -0
- trueppm_api/apps/projects/migrations/0092_program_target_date.py +22 -0
- trueppm_api/apps/projects/migrations/0093_cross_project_dependency_consent.py +58 -0
- trueppm_api/apps/projects/migrations/0094_project_decisions_policy.py +90 -0
- trueppm_api/apps/projects/migrations/0095_poker_session_vote.py +127 -0
- trueppm_api/apps/projects/migrations/0095_squashed_0097_release_0_3.py +225 -0
- trueppm_api/apps/projects/migrations/0096_crossprojectslipconflict.py +91 -0
- trueppm_api/apps/projects/migrations/0097_backlogitem_backlogitem_tags_gin_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0098_historicalproject_show_baselines_and_more.py +52 -0
- trueppm_api/apps/projects/migrations/0099_apitoken_scopes.py +48 -0
- trueppm_api/apps/projects/migrations/0100_task_dep_serverver_idx_concurrent.py +61 -0
- trueppm_api/apps/projects/migrations/0101_dependency_deleted_at_task_deleted_at.py +22 -0
- trueppm_api/apps/projects/migrations/0102_project_deleted_at_project_proj_isdel_deletedat_idx.py +25 -0
- trueppm_api/apps/projects/migrations/0103_taskdurationchangeevent_task_dur_evt_sprint_idx.py +18 -0
- trueppm_api/apps/projects/migrations/0104_historicalproject_stale_task_threshold_days_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0104_trash_restore_1113.py +38 -0
- trueppm_api/apps/projects/migrations/0105_historical_changelog_indexes.py +62 -0
- trueppm_api/apps/projects/migrations/0106_boardsavedview_schema_version.py +20 -0
- trueppm_api/apps/projects/migrations/0107_task_activity_event.py +70 -0
- trueppm_api/apps/projects/migrations/0108_remove_apitoken_api_token_scope_xor_and_more.py +117 -0
- trueppm_api/apps/projects/migrations/0109_projectexportjob.py +82 -0
- trueppm_api/apps/projects/migrations/0110_sharelink.py +123 -0
- trueppm_api/apps/projects/migrations/0111_projectcalendarlayer.py +67 -0
- trueppm_api/apps/projects/migrations/0112_sharelink_expires_at_alter_sharelink_content_kind.py +31 -0
- trueppm_api/apps/projects/migrations/0113_alter_historicaltask_percent_complete_and_more.py +35 -0
- trueppm_api/apps/projects/migrations/0114_historicalproject_default_member_role_and_more.py +28 -0
- trueppm_api/apps/projects/migrations/0115_historicalproject_end_date_shift_threshold_days_and_more.py +22 -0
- trueppm_api/apps/projects/migrations/0116_alter_taskactivityevent_event_type.py +28 -0
- trueppm_api/apps/projects/migrations/0117_label_tasklabel_label_tasks_and_more.py +126 -0
- trueppm_api/apps/projects/migrations/0118_programexportjob.py +82 -0
- trueppm_api/apps/projects/migrations/0119_historicalprogram_calendar_program_calendar.py +36 -0
- trueppm_api/apps/projects/migrations/0120_alter_backlogitem_item_type.py +29 -0
- trueppm_api/apps/projects/migrations/0121_task_three_point_estimate_ordered.py +64 -0
- trueppm_api/apps/projects/migrations/0122_task_relation.py +173 -0
- trueppm_api/apps/projects/migrations/0123_remove_historicalproject_agile_features_and_more.py +20 -0
- trueppm_api/apps/projects/migrations/0124_dependency_is_driving.py +17 -0
- trueppm_api/apps/projects/migrations/0125_historicalprogram_estimation_scale_and_more.py +68 -0
- trueppm_api/apps/projects/migrations/0126_projectcustomfield_show_on_card_taskcustomfieldvalue.py +80 -0
- trueppm_api/apps/projects/migrations/0127_historicalprogram_mcp_enabled_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0128_acceptancecriterion_sync_seq_apitoken_sync_seq_and_more.py +192 -0
- trueppm_api/apps/projects/migrations/0129_sharelink_show_milestone_dates.py +20 -0
- trueppm_api/apps/projects/migrations/0130_programimportjob.py +85 -0
- trueppm_api/apps/projects/migrations/0131_historicalprogram_sprint_picker_ready_only_default_and_more.py +32 -0
- trueppm_api/apps/projects/migrations/0132_task_scheduled_start.py +17 -0
- trueppm_api/apps/projects/migrations/0133_project_status_date_floor_armed_at.py +17 -0
- trueppm_api/apps/projects/migrations/0134_task_edited_at_task_seeded_at_task_source_id_and_more.py +65 -0
- trueppm_api/apps/projects/migrations/0135_projecttemplate_templateapplication_and_more.py +175 -0
- trueppm_api/apps/projects/migrations/0136_schedule_outline_batch_operations.py +118 -0
- trueppm_api/apps/projects/migrations/0137_collapse_dual_scope_api_tokens.py +54 -0
- trueppm_api/apps/projects/migrations/0138_apitokenauditentry_api_token_audit_owner_idx.py +18 -0
- trueppm_api/apps/projects/migrations/0139_historicaltask_duration_unit_task_duration_unit.py +32 -0
- trueppm_api/apps/projects/migrations/0140_historicaltask_auto_container_and_more.py +113 -0
- trueppm_api/apps/projects/migrations/0141_historicalproject_draft_started_at_and_more.py +44 -0
- trueppm_api/apps/projects/migrations/0142_baseline_calendar_hours_per_day_and_more.py +31 -0
- trueppm_api/apps/projects/migrations/0143_sprintcloserequest_failure_reason_and_more.py +42 -0
- trueppm_api/apps/projects/migrations/0144_projecttemplate_source_project_and_more.py +35 -0
- trueppm_api/apps/projects/migrations/0145_seed_bundled_templates.py +65 -0
- trueppm_api/apps/projects/migrations/0146_historicaltask_board_lane_task_board_lane.py +32 -0
- trueppm_api/apps/projects/migrations/0147_structuraloperation.py +128 -0
- trueppm_api/apps/projects/migrations/0148_task_unique_task_wbs_path_per_project_live.py +76 -0
- trueppm_api/apps/projects/migrations/0149_drop_workshops_tables.py +42 -0
- trueppm_api/apps/projects/migrations/0150_clear_uncommitted_cpm_output.py +130 -0
- trueppm_api/apps/projects/migrations/0151_config_notice_request.py +70 -0
- trueppm_api/apps/projects/migrations/0152_aggregate_task_wbs_name_idx.py +20 -0
- trueppm_api/apps/projects/migrations/0153_program_sample_anchor_date.py +22 -0
- trueppm_api/apps/projects/migrations/__init__.py +0 -0
- trueppm_api/apps/projects/models.py +8946 -0
- trueppm_api/apps/projects/poker_services.py +154 -0
- trueppm_api/apps/projects/poker_views.py +398 -0
- trueppm_api/apps/projects/product_backlog_services.py +450 -0
- trueppm_api/apps/projects/program_label_services.py +94 -0
- trueppm_api/apps/projects/program_label_views.py +189 -0
- trueppm_api/apps/projects/program_rollup.py +575 -0
- trueppm_api/apps/projects/program_schedule.py +745 -0
- trueppm_api/apps/projects/program_views.py +3220 -0
- trueppm_api/apps/projects/project_templates.py +422 -0
- trueppm_api/apps/projects/receivers.py +234 -0
- trueppm_api/apps/projects/refusal_codes.py +140 -0
- trueppm_api/apps/projects/reorder_services.py +145 -0
- trueppm_api/apps/projects/restructure_hooks.py +54 -0
- trueppm_api/apps/projects/retro_board_services.py +368 -0
- trueppm_api/apps/projects/retro_services.py +285 -0
- trueppm_api/apps/projects/risk_import.py +396 -0
- trueppm_api/apps/projects/row_identity.py +113 -0
- trueppm_api/apps/projects/schema_migrations.py +228 -0
- trueppm_api/apps/projects/schemas/seed_v1.json +493 -0
- trueppm_api/apps/projects/schemas/seed_v2.json +892 -0
- trueppm_api/apps/projects/seed/__init__.py +61 -0
- trueppm_api/apps/projects/seed/exporter.py +1644 -0
- trueppm_api/apps/projects/seed/forecast_backfill.py +247 -0
- trueppm_api/apps/projects/seed/importer.py +2390 -0
- trueppm_api/apps/projects/seed/reanchor.py +551 -0
- trueppm_api/apps/projects/seed/reldates.py +126 -0
- trueppm_api/apps/projects/seed/replace.py +126 -0
- trueppm_api/apps/projects/seed/replay.py +1691 -0
- trueppm_api/apps/projects/seed/replay_ctx.py +41 -0
- trueppm_api/apps/projects/seed/samples.py +427 -0
- trueppm_api/apps/projects/seed/validation.py +1149 -0
- trueppm_api/apps/projects/serializers.py +12310 -0
- trueppm_api/apps/projects/services.py +6966 -0
- trueppm_api/apps/projects/share_serializers.py +132 -0
- trueppm_api/apps/projects/share_services.py +325 -0
- trueppm_api/apps/projects/share_views.py +374 -0
- trueppm_api/apps/projects/sharing_settings.py +187 -0
- trueppm_api/apps/projects/signal_privacy_services.py +909 -0
- trueppm_api/apps/projects/signal_privacy_views.py +476 -0
- trueppm_api/apps/projects/signals.py +132 -0
- trueppm_api/apps/projects/slip_conflict.py +266 -0
- trueppm_api/apps/projects/sprint_cadence.py +453 -0
- trueppm_api/apps/projects/sprint_picker_settings.py +132 -0
- trueppm_api/apps/projects/standup.py +304 -0
- trueppm_api/apps/projects/standup_views.py +99 -0
- trueppm_api/apps/projects/structural_operation_services.py +727 -0
- trueppm_api/apps/projects/structural_operation_views.py +311 -0
- trueppm_api/apps/projects/surface_visibility.py +126 -0
- trueppm_api/apps/projects/task_batch_services.py +379 -0
- trueppm_api/apps/projects/task_bulk.py +1152 -0
- trueppm_api/apps/projects/task_classification.py +561 -0
- trueppm_api/apps/projects/task_classification_defaults.py +95 -0
- trueppm_api/apps/projects/task_duration_settings.py +142 -0
- trueppm_api/apps/projects/task_grouping.py +754 -0
- trueppm_api/apps/projects/tasks.py +1917 -0
- trueppm_api/apps/projects/template_divergence.py +168 -0
- trueppm_api/apps/projects/template_services.py +160 -0
- trueppm_api/apps/projects/template_tasks.py +258 -0
- trueppm_api/apps/projects/template_views.py +799 -0
- trueppm_api/apps/projects/throttles.py +311 -0
- trueppm_api/apps/projects/urls.py +1011 -0
- trueppm_api/apps/projects/utilization.py +885 -0
- trueppm_api/apps/projects/views.py +20572 -0
- trueppm_api/apps/projects/wbs_paths.py +119 -0
- trueppm_api/apps/resources/__init__.py +0 -0
- trueppm_api/apps/resources/apps.py +8 -0
- trueppm_api/apps/resources/capacity.py +121 -0
- trueppm_api/apps/resources/migrations/0001_initial.py +68 -0
- trueppm_api/apps/resources/migrations/0001_squashed_0013_alter_projectresource_unique_together_and_more.py +281 -0
- trueppm_api/apps/resources/migrations/0002_alter_uuid_pk_serialize_false.py +36 -0
- trueppm_api/apps/resources/migrations/0003_resource_soft_delete.py +30 -0
- trueppm_api/apps/resources/migrations/0004_add_taskresource_resource_index.py +25 -0
- trueppm_api/apps/resources/migrations/0005_resource_job_role.py +20 -0
- trueppm_api/apps/resources/migrations/0006_skill.py +34 -0
- trueppm_api/apps/resources/migrations/0007_resource_skill.py +63 -0
- trueppm_api/apps/resources/migrations/0008_project_resource.py +63 -0
- trueppm_api/apps/resources/migrations/0009_task_skill_requirement.py +60 -0
- trueppm_api/apps/resources/migrations/0010_alter_resourceskill_options_and_more.py +20 -0
- trueppm_api/apps/resources/migrations/0011_alter_projectresource_notes.py +17 -0
- trueppm_api/apps/resources/migrations/0012_add_resource_user_fk.py +26 -0
- trueppm_api/apps/resources/migrations/0013_alter_projectresource_unique_together_and_more.py +53 -0
- trueppm_api/apps/resources/migrations/0014_projectresource_sync_seq_resource_sync_seq_and_more.py +37 -0
- trueppm_api/apps/resources/migrations/0015_projectresource_deactivated_with_resource.py +17 -0
- trueppm_api/apps/resources/migrations/__init__.py +0 -0
- trueppm_api/apps/resources/models.py +298 -0
- trueppm_api/apps/resources/serializers.py +426 -0
- trueppm_api/apps/resources/services.py +465 -0
- trueppm_api/apps/resources/urls.py +28 -0
- trueppm_api/apps/resources/views.py +1749 -0
- trueppm_api/apps/scheduling/__init__.py +0 -0
- trueppm_api/apps/scheduling/apps.py +153 -0
- trueppm_api/apps/scheduling/calendars.py +119 -0
- trueppm_api/apps/scheduling/deadletter.py +91 -0
- trueppm_api/apps/scheduling/forecast_history_settings.py +215 -0
- trueppm_api/apps/scheduling/forecast_staleness.py +313 -0
- trueppm_api/apps/scheduling/graph_guard.py +263 -0
- trueppm_api/apps/scheduling/management/__init__.py +0 -0
- trueppm_api/apps/scheduling/management/commands/__init__.py +0 -0
- trueppm_api/apps/scheduling/management/commands/prune_forecast_snapshots.py +53 -0
- trueppm_api/apps/scheduling/migrations/0001_failed_task.py +62 -0
- trueppm_api/apps/scheduling/migrations/0001_squashed_0007_projectforecastsnapshot.py +309 -0
- trueppm_api/apps/scheduling/migrations/0002_schedulerequest.py +78 -0
- trueppm_api/apps/scheduling/migrations/0003_add_schedule_request_reason.py +27 -0
- trueppm_api/apps/scheduling/migrations/0004_velocity_suggestion.py +94 -0
- trueppm_api/apps/scheduling/migrations/0005_montecarlorun.py +61 -0
- trueppm_api/apps/scheduling/migrations/0006_montecarlorun_distribution.py +17 -0
- trueppm_api/apps/scheduling/migrations/0007_projectforecastsnapshot.py +65 -0
- trueppm_api/apps/scheduling/migrations/0008_alter_schedulerequest_reason.py +28 -0
- trueppm_api/apps/scheduling/migrations/0008_folded_schedulerequest_reason.py +34 -0
- trueppm_api/apps/scheduling/migrations/0009_alter_schedulerequest_reason.py +29 -0
- trueppm_api/apps/scheduling/migrations/0010_failedtask_resolution_note_failedtask_resolved_at_and_more.py +43 -0
- trueppm_api/apps/scheduling/migrations/0011_failedtask_project.py +26 -0
- trueppm_api/apps/scheduling/migrations/0012_montecarlorun_diagnostic.py +17 -0
- trueppm_api/apps/scheduling/migrations/0013_montecarlorun_status_date.py +17 -0
- trueppm_api/apps/scheduling/migrations/0014_montecarlorun_plan_version.py +17 -0
- trueppm_api/apps/scheduling/migrations/__init__.py +0 -0
- trueppm_api/apps/scheduling/models.py +516 -0
- trueppm_api/apps/scheduling/receivers.py +160 -0
- trueppm_api/apps/scheduling/risk_premium.py +283 -0
- trueppm_api/apps/scheduling/serializers.py +564 -0
- trueppm_api/apps/scheduling/services.py +1182 -0
- trueppm_api/apps/scheduling/signals.py +56 -0
- trueppm_api/apps/scheduling/tasks.py +1984 -0
- trueppm_api/apps/scheduling/telemetry.py +80 -0
- trueppm_api/apps/scheduling/units.py +35 -0
- trueppm_api/apps/scheduling/urls.py +57 -0
- trueppm_api/apps/scheduling/views.py +2564 -0
- trueppm_api/apps/sso/__init__.py +0 -0
- trueppm_api/apps/sso/apps.py +19 -0
- trueppm_api/apps/sso/extensions.py +130 -0
- trueppm_api/apps/sso/management/__init__.py +0 -0
- trueppm_api/apps/sso/management/commands/__init__.py +0 -0
- trueppm_api/apps/sso/management/commands/seed_sso_keycloak.py +172 -0
- trueppm_api/apps/sso/migrations/0001_initial.py +119 -0
- trueppm_api/apps/sso/migrations/0002_remove_oidcprovider_workspace_ssoproviderpolicy_and_more.py +169 -0
- trueppm_api/apps/sso/migrations/__init__.py +0 -0
- trueppm_api/apps/sso/models.py +148 -0
- trueppm_api/apps/sso/serializers.py +437 -0
- trueppm_api/apps/sso/services.py +1385 -0
- trueppm_api/apps/sso/urls.py +47 -0
- trueppm_api/apps/sso/views.py +935 -0
- trueppm_api/apps/sync/__init__.py +0 -0
- trueppm_api/apps/sync/apps.py +15 -0
- trueppm_api/apps/sync/backfill.py +208 -0
- trueppm_api/apps/sync/broadcast.py +320 -0
- trueppm_api/apps/sync/conflict.py +262 -0
- trueppm_api/apps/sync/consumers.py +471 -0
- trueppm_api/apps/sync/migrations/0001_initial.py +59 -0
- trueppm_api/apps/sync/migrations/0001_squashed_0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +77 -0
- trueppm_api/apps/sync/migrations/0002_remove_syncbatch_syncbatch_project_client_batch_uniq_and_more.py +41 -0
- trueppm_api/apps/sync/migrations/0003_boardevent.py +46 -0
- trueppm_api/apps/sync/migrations/0004_seed_sync_seq.py +24 -0
- trueppm_api/apps/sync/migrations/0005_program_sync_sequence.py +35 -0
- trueppm_api/apps/sync/migrations/__init__.py +0 -0
- trueppm_api/apps/sync/models.py +225 -0
- trueppm_api/apps/sync/pagination.py +231 -0
- trueppm_api/apps/sync/sequence.py +371 -0
- trueppm_api/apps/sync/serializers.py +687 -0
- trueppm_api/apps/sync/tasks.py +260 -0
- trueppm_api/apps/sync/throttles.py +175 -0
- trueppm_api/apps/sync/upload.py +501 -0
- trueppm_api/apps/sync/urls.py +17 -0
- trueppm_api/apps/sync/views.py +930 -0
- trueppm_api/apps/sync/ws_auth.py +193 -0
- trueppm_api/apps/taskruns/__init__.py +0 -0
- trueppm_api/apps/taskruns/apps.py +11 -0
- trueppm_api/apps/taskruns/migrations/0001_initial.py +92 -0
- trueppm_api/apps/taskruns/migrations/__init__.py +0 -0
- trueppm_api/apps/taskruns/models.py +68 -0
- trueppm_api/apps/taskruns/serializers.py +92 -0
- trueppm_api/apps/taskruns/tasks.py +60 -0
- trueppm_api/apps/taskruns/tracker.py +286 -0
- trueppm_api/apps/taskruns/urls.py +46 -0
- trueppm_api/apps/taskruns/views.py +195 -0
- trueppm_api/apps/teams/__init__.py +8 -0
- trueppm_api/apps/teams/apps.py +16 -0
- trueppm_api/apps/teams/migrations/0001_initial.py +139 -0
- trueppm_api/apps/teams/migrations/0001_squashed_0002_default_teams.py +142 -0
- trueppm_api/apps/teams/migrations/0002_default_teams.py +62 -0
- trueppm_api/apps/teams/migrations/0003_team_sync_seq_teammembership_sync_seq.py +22 -0
- trueppm_api/apps/teams/migrations/__init__.py +0 -0
- trueppm_api/apps/teams/models.py +145 -0
- trueppm_api/apps/teams/permissions.py +149 -0
- trueppm_api/apps/teams/serializers.py +95 -0
- trueppm_api/apps/teams/services.py +434 -0
- trueppm_api/apps/teams/signals.py +61 -0
- trueppm_api/apps/teams/urls.py +34 -0
- trueppm_api/apps/teams/views.py +201 -0
- trueppm_api/apps/timetracking/__init__.py +0 -0
- trueppm_api/apps/timetracking/apps.py +9 -0
- trueppm_api/apps/timetracking/migrations/0001_initial.py +117 -0
- trueppm_api/apps/timetracking/migrations/0002_timesheetsubmission.py +46 -0
- trueppm_api/apps/timetracking/migrations/0003_timeentry_deleted_at_timeentry_deleted_by.py +31 -0
- trueppm_api/apps/timetracking/migrations/0004_timeentry_sync_seq_and_more.py +24 -0
- trueppm_api/apps/timetracking/migrations/__init__.py +0 -0
- trueppm_api/apps/timetracking/models.py +178 -0
- trueppm_api/apps/timetracking/serializers.py +175 -0
- trueppm_api/apps/timetracking/services.py +117 -0
- trueppm_api/apps/timetracking/urls.py +47 -0
- trueppm_api/apps/timetracking/views.py +526 -0
- trueppm_api/apps/webhooks/__init__.py +0 -0
- trueppm_api/apps/webhooks/apps.py +8 -0
- trueppm_api/apps/webhooks/backfill.py +90 -0
- trueppm_api/apps/webhooks/dispatch.py +144 -0
- trueppm_api/apps/webhooks/migrations/0001_initial.py +146 -0
- trueppm_api/apps/webhooks/migrations/0001_squashed_0007_sprint_lifecycle_events.py +243 -0
- trueppm_api/apps/webhooks/migrations/0002_alter_webhook_events.py +32 -0
- trueppm_api/apps/webhooks/migrations/0003_delivery_status_created_idx.py +17 -0
- trueppm_api/apps/webhooks/migrations/0004_webhook_program_alter_webhook_project_and_more.py +64 -0
- trueppm_api/apps/webhooks/migrations/0005_webhook_delivery_sequence_numbers.py +30 -0
- trueppm_api/apps/webhooks/migrations/0006_webhook_format_alter_webhook_events_and_more.py +61 -0
- trueppm_api/apps/webhooks/migrations/0007_sprint_lifecycle_events.py +63 -0
- trueppm_api/apps/webhooks/migrations/0008_risk_baseline_comment_events.py +73 -0
- trueppm_api/apps/webhooks/migrations/0009_encrypt_webhook_secret_and_failure_counters.py +90 -0
- trueppm_api/apps/webhooks/migrations/__init__.py +0 -0
- trueppm_api/apps/webhooks/models.py +372 -0
- trueppm_api/apps/webhooks/serializers.py +318 -0
- trueppm_api/apps/webhooks/tasks.py +663 -0
- trueppm_api/apps/webhooks/urls.py +22 -0
- trueppm_api/apps/webhooks/views.py +383 -0
- trueppm_api/apps/workflow_engine/__init__.py +0 -0
- trueppm_api/apps/workflow_engine/apps.py +23 -0
- trueppm_api/apps/workflow_engine/migrations/0001_initial.py +230 -0
- trueppm_api/apps/workflow_engine/migrations/0001_squashed_0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +234 -0
- trueppm_api/apps/workflow_engine/migrations/0002_remove_workflowhistoryevent_workflow_history_lookup_idx.py +16 -0
- trueppm_api/apps/workflow_engine/migrations/0003_workflowoutboxrow_workflow_outbox_recovery_idx.py +21 -0
- trueppm_api/apps/workflow_engine/migrations/__init__.py +0 -0
- trueppm_api/apps/workflow_engine/models.py +265 -0
- trueppm_api/apps/workflow_engine/tasks.py +368 -0
- trueppm_api/apps/workspace/__init__.py +0 -0
- trueppm_api/apps/workspace/apps.py +10 -0
- trueppm_api/apps/workspace/backfill.py +111 -0
- trueppm_api/apps/workspace/export.py +242 -0
- trueppm_api/apps/workspace/migrations/0001_initial.py +326 -0
- trueppm_api/apps/workspace/migrations/0001_squashed_0014_workspacemembership_availability_effective_from_and_more.py +829 -0
- trueppm_api/apps/workspace/migrations/0002_remove_workspace_fiscal_year_start_and_more.py +85 -0
- trueppm_api/apps/workspace/migrations/0003_workspaceexportjob.py +73 -0
- trueppm_api/apps/workspace/migrations/0004_alter_workspaceinvite_status.py +27 -0
- trueppm_api/apps/workspace/migrations/0005_workspace_iteration_label_and_more.py +26 -0
- trueppm_api/apps/workspace/migrations/0006_alter_groupmembership_unique_together_and_more.py +45 -0
- trueppm_api/apps/workspace/migrations/0007_workspace_public_sharing_override_policy.py +21 -0
- trueppm_api/apps/workspace/migrations/0008_workspace_mc_history_attribution_audience_and_more.py +44 -0
- trueppm_api/apps/workspace/migrations/0009_workspace_methodology_and_more.py +186 -0
- trueppm_api/apps/workspace/migrations/0010_workspace_logo.py +38 -0
- trueppm_api/apps/workspace/migrations/0011_historicalworkspace_task_duration_change_percent_override_policy_and_more.py +56 -0
- trueppm_api/apps/workspace/migrations/0012_historicalworkspace_allowed_attachment_types_and_more.py +64 -0
- trueppm_api/apps/workspace/migrations/0013_auditevent.py +70 -0
- trueppm_api/apps/workspace/migrations/0014_workspacemembership_availability_effective_from_and_more.py +39 -0
- trueppm_api/apps/workspace/migrations/0015_trash_restore_1113.py +34 -0
- trueppm_api/apps/workspace/migrations/0016_historicalworkspace_calendar_and_more.py +55 -0
- trueppm_api/apps/workspace/migrations/0017_historicalworkspace_estimation_scale_and_more.py +38 -0
- trueppm_api/apps/workspace/migrations/0018_historicalworkspace_feedback_enabled_and_more.py +32 -0
- trueppm_api/apps/workspace/migrations/0019_historicalworkspace_mcp_enabled_and_more.py +22 -0
- trueppm_api/apps/workspace/migrations/0020_viewer_ordinal_one.py +34 -0
- trueppm_api/apps/workspace/migrations/0021_group_sync_seq_groupmembership_sync_seq_and_more.py +27 -0
- trueppm_api/apps/workspace/migrations/0022_historicalworkspace_sprint_picker_ready_only_default_and_more.py +22 -0
- trueppm_api/apps/workspace/migrations/0023_alter_auditevent_event_type.py +32 -0
- trueppm_api/apps/workspace/migrations/0024_alter_auditevent_event_type.py +33 -0
- trueppm_api/apps/workspace/migrations/0025_alter_auditevent_event_type.py +36 -0
- trueppm_api/apps/workspace/migrations/0026_alter_auditevent_event_type.py +37 -0
- trueppm_api/apps/workspace/migrations/0027_alter_auditevent_event_type.py +42 -0
- trueppm_api/apps/workspace/migrations/__init__.py +0 -0
- trueppm_api/apps/workspace/models.py +857 -0
- trueppm_api/apps/workspace/permissions.py +187 -0
- trueppm_api/apps/workspace/serializers.py +590 -0
- trueppm_api/apps/workspace/services.py +919 -0
- trueppm_api/apps/workspace/signals.py +37 -0
- trueppm_api/apps/workspace/tasks.py +555 -0
- trueppm_api/apps/workspace/urls.py +113 -0
- trueppm_api/apps/workspace/views.py +1502 -0
- trueppm_api/asgi.py +42 -0
- trueppm_api/celery.py +31 -0
- trueppm_api/core/__init__.py +0 -0
- trueppm_api/core/admin_site.py +206 -0
- trueppm_api/core/auth_views.py +916 -0
- trueppm_api/core/constant_time.py +51 -0
- trueppm_api/core/csp.py +54 -0
- trueppm_api/core/db.py +87 -0
- trueppm_api/core/db_session.py +37 -0
- trueppm_api/core/exception_handlers.py +157 -0
- trueppm_api/core/export_downloads.py +56 -0
- trueppm_api/core/extension_providers.py +56 -0
- trueppm_api/core/extension_signals.py +147 -0
- trueppm_api/core/idempotent.py +260 -0
- trueppm_api/core/metadata.py +42 -0
- trueppm_api/core/middleware.py +51 -0
- trueppm_api/core/openapi.py +949 -0
- trueppm_api/core/password_reset.py +500 -0
- trueppm_api/core/protect_conflict.py +100 -0
- trueppm_api/core/ratelimit.py +148 -0
- trueppm_api/core/redis_throttle.py +82 -0
- trueppm_api/core/refresh_cookie_policy.py +156 -0
- trueppm_api/core/request_body.py +86 -0
- trueppm_api/core/security_checks.py +884 -0
- trueppm_api/core/storage_config.py +105 -0
- trueppm_api/core/text_decode.py +168 -0
- trueppm_api/core/throttling.py +344 -0
- trueppm_api/core/valkey.py +214 -0
- trueppm_api/core/valkey_cache.py +66 -0
- trueppm_api/core/valkey_checks.py +104 -0
- trueppm_api/core/valkey_config.py +39 -0
- trueppm_api/core/worker_broker_wait.py +149 -0
- trueppm_api/core/worker_heartbeat.py +134 -0
- trueppm_api/fields.py +36 -0
- trueppm_api/permissions.py +30 -0
- trueppm_api/py.typed +0 -0
- trueppm_api/routing.py +11 -0
- trueppm_api/settings/__init__.py +0 -0
- trueppm_api/settings/base.py +2455 -0
- trueppm_api/settings/dev.py +273 -0
- trueppm_api/settings/prod.py +228 -0
- trueppm_api/urls.py +153 -0
- trueppm_api/workflows/__init__.py +38 -0
- trueppm_api/workflows/backends/__init__.py +6 -0
- trueppm_api/workflows/backends/default.py +297 -0
- trueppm_api/workflows/consumers/__init__.py +11 -0
- trueppm_api/workflows/consumers/requeue_failed_task.py +65 -0
- trueppm_api/workflows/interface.py +148 -0
- trueppm_api/workflows/registry.py +106 -0
- trueppm_api/workflows/services.py +90 -0
- trueppm_api/wsgi.py +11 -0
- trueppm_api-0.4.0b2.dist-info/METADATA +70 -0
- trueppm_api-0.4.0b2.dist-info/RECORD +759 -0
- trueppm_api-0.4.0b2.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
"""Signal-privacy policy endpoints (ADR-0104 §1.1 + Amendment A / #930).
|
|
2
|
+
|
|
3
|
+
Surface:
|
|
4
|
+
GET /api/v1/projects/<project_pk>/signal-privacy/ — read posture (+ open proposals)
|
|
5
|
+
PATCH /api/v1/projects/<project_pk>/signal-privacy/ — set one audience
|
|
6
|
+
POST .../signal-privacy/raise-ceiling/ — raise → propose; lower → apply
|
|
7
|
+
POST /api/v1/projects/<project_pk>/signal-privacy/ratchet-down/
|
|
8
|
+
GET /api/v1/projects/<project_pk>/signal-privacy/ceiling-proposals/ — list (team-readable)
|
|
9
|
+
POST .../signal-privacy/ceiling-proposals/<proposal_pk>/vote/ — cast/change a vote
|
|
10
|
+
POST .../signal-privacy/ceiling-proposals/<proposal_pk>/withdraw/ — proposer cancels
|
|
11
|
+
|
|
12
|
+
Write gates:
|
|
13
|
+
* set-audience / ratchet — the facilitator's dial (Scrum-Master facet, ADR-0078
|
|
14
|
+
/ #927) OR a project Admin (role >= ADMIN). Bounded by the ceiling, so it can
|
|
15
|
+
never widen exposure past what the team authorized.
|
|
16
|
+
* raise-ceiling — the team-owned act (ADR-0104 §1.1 + Amendment A / #930).
|
|
17
|
+
A *raise* opens a ratification proposal (facilitator/Admin may propose); the
|
|
18
|
+
ceiling applies only once the team ratifies (strict majority of the team roster).
|
|
19
|
+
A *lower* and all set-audience moves stay immediate single actions. There is no
|
|
20
|
+
management bypass — that would reopen the §2 back-door the model closes.
|
|
21
|
+
* vote — any active team member (TeamMembership of the default
|
|
22
|
+
team). A non-team project Admin/PM cannot vote (Amendment A.2).
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from typing import Any
|
|
28
|
+
|
|
29
|
+
from drf_spectacular.utils import OpenApiResponse, extend_schema
|
|
30
|
+
from rest_framework import serializers, status
|
|
31
|
+
from rest_framework.exceptions import PermissionDenied
|
|
32
|
+
from rest_framework.generics import get_object_or_404
|
|
33
|
+
from rest_framework.permissions import IsAuthenticated
|
|
34
|
+
from rest_framework.request import Request
|
|
35
|
+
from rest_framework.response import Response
|
|
36
|
+
from rest_framework.views import APIView
|
|
37
|
+
|
|
38
|
+
from trueppm_api.apps.access.models import Role
|
|
39
|
+
from trueppm_api.apps.access.permissions import (
|
|
40
|
+
IsProjectMember,
|
|
41
|
+
IsProjectNotArchived,
|
|
42
|
+
_membership_role,
|
|
43
|
+
)
|
|
44
|
+
from trueppm_api.apps.projects.models import (
|
|
45
|
+
CeilingRaiseStatus,
|
|
46
|
+
Project,
|
|
47
|
+
ProjectSignalPrivacyPolicy,
|
|
48
|
+
SignalAudience,
|
|
49
|
+
SignalCeilingRaiseProposal,
|
|
50
|
+
)
|
|
51
|
+
from trueppm_api.apps.projects.signal_privacy_services import (
|
|
52
|
+
SIGNAL_KEYS,
|
|
53
|
+
CeilingProposalConflict,
|
|
54
|
+
cast_ceiling_vote,
|
|
55
|
+
get_or_create_policy,
|
|
56
|
+
live_open_proposals,
|
|
57
|
+
proposal_tally,
|
|
58
|
+
propose_or_apply_ceiling_change,
|
|
59
|
+
ratchet_down_to_team,
|
|
60
|
+
requester_signal_tier,
|
|
61
|
+
set_signal_audience,
|
|
62
|
+
withdraw_ceiling_proposal,
|
|
63
|
+
)
|
|
64
|
+
from trueppm_api.apps.teams.services import (
|
|
65
|
+
has_team_facet,
|
|
66
|
+
is_team_member,
|
|
67
|
+
team_member_user_ids,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# ---------------------------------------------------------------------------
|
|
71
|
+
# Serializers (declared here; drf-spectacular discovers them for the schema)
|
|
72
|
+
# ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class _SignalPairSerializer(serializers.Serializer[Any]):
|
|
76
|
+
audience = serializers.ChoiceField(choices=SignalAudience.choices)
|
|
77
|
+
ceiling = serializers.ChoiceField(choices=SignalAudience.choices)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class CeilingVoteSerializer(serializers.Serializer[Any]):
|
|
81
|
+
"""One team member's vote on a ceiling-raise proposal (team-readable)."""
|
|
82
|
+
|
|
83
|
+
voter = serializers.CharField()
|
|
84
|
+
choice = serializers.CharField()
|
|
85
|
+
created_at = serializers.DateTimeField()
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class CeilingProposalSerializer(serializers.Serializer[Any]):
|
|
89
|
+
"""A ceiling-raise ratification proposal with its live tally (ADR-0104 Amendment A)."""
|
|
90
|
+
|
|
91
|
+
id = serializers.CharField()
|
|
92
|
+
signal = serializers.CharField()
|
|
93
|
+
from_ceiling = serializers.ChoiceField(choices=SignalAudience.choices)
|
|
94
|
+
to_ceiling = serializers.ChoiceField(choices=SignalAudience.choices)
|
|
95
|
+
status = serializers.ChoiceField(choices=CeilingRaiseStatus.choices)
|
|
96
|
+
proposed_by = serializers.CharField(allow_null=True)
|
|
97
|
+
created_at = serializers.DateTimeField()
|
|
98
|
+
expires_at = serializers.DateTimeField()
|
|
99
|
+
resolved_at = serializers.DateTimeField(allow_null=True)
|
|
100
|
+
approve_count = serializers.IntegerField()
|
|
101
|
+
reject_count = serializers.IntegerField()
|
|
102
|
+
eligible_count = serializers.IntegerField()
|
|
103
|
+
threshold = serializers.IntegerField()
|
|
104
|
+
# Amendment C: eligibility is evaluated at tally time, so the counts above can move
|
|
105
|
+
# when the roster does. This says how many cast votes are no longer counted, so a
|
|
106
|
+
# shrinking tally is explainable rather than mysterious. Normally 0.
|
|
107
|
+
disregarded_vote_count = serializers.IntegerField()
|
|
108
|
+
your_vote = serializers.CharField(allow_null=True)
|
|
109
|
+
can_vote = serializers.BooleanField()
|
|
110
|
+
votes = CeilingVoteSerializer(many=True)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class SignalPrivacyPolicySerializer(serializers.Serializer[Any]):
|
|
114
|
+
"""Read shape: the resolved per-signal posture + what the requester may do."""
|
|
115
|
+
|
|
116
|
+
signals = serializers.DictField(child=_SignalPairSerializer())
|
|
117
|
+
requester_tier = serializers.ChoiceField(choices=SignalAudience.choices, allow_null=True)
|
|
118
|
+
can_set_audience = serializers.BooleanField()
|
|
119
|
+
can_raise_ceiling = serializers.BooleanField()
|
|
120
|
+
can_vote = serializers.BooleanField()
|
|
121
|
+
# Per-signal live ratification proposal (Amendment A) — Sarah's pending indicator.
|
|
122
|
+
open_proposals = serializers.DictField(child=CeilingProposalSerializer())
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class SetAudienceSerializer(serializers.Serializer[Any]):
|
|
126
|
+
"""PATCH body — move one signal's audience within [TEAM, ceiling]."""
|
|
127
|
+
|
|
128
|
+
signal = serializers.ChoiceField(choices=[(k, k) for k in SIGNAL_KEYS])
|
|
129
|
+
audience = serializers.ChoiceField(choices=SignalAudience.choices)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class RaiseCeilingSerializer(serializers.Serializer[Any]):
|
|
133
|
+
"""POST body — set one signal's ceiling (the team-owned authorization)."""
|
|
134
|
+
|
|
135
|
+
signal = serializers.ChoiceField(choices=[(k, k) for k in SIGNAL_KEYS])
|
|
136
|
+
ceiling = serializers.ChoiceField(choices=SignalAudience.choices)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class CeilingVoteRequestSerializer(serializers.Serializer[Any]):
|
|
140
|
+
"""POST body — cast or change a vote on a ceiling-raise proposal."""
|
|
141
|
+
|
|
142
|
+
choice = serializers.ChoiceField(choices=[("approve", "approve"), ("reject", "reject")])
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
# ---------------------------------------------------------------------------
|
|
146
|
+
# Shared helpers
|
|
147
|
+
# ---------------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _is_facilitator_or_admin(request: Request, project_id: Any) -> bool:
|
|
151
|
+
"""Facilitator (Scrum-Master facet) OR project Admin — the propose/set-audience gate."""
|
|
152
|
+
role = _membership_role(request, project_id)
|
|
153
|
+
if role is not None and role >= Role.ADMIN:
|
|
154
|
+
return True
|
|
155
|
+
return has_team_facet(request.user, project_id, "is_scrum_master")
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _serialize_proposal(
|
|
159
|
+
proposal: SignalCeilingRaiseProposal,
|
|
160
|
+
request: Request,
|
|
161
|
+
voter_ids: set[Any] | None = None,
|
|
162
|
+
) -> dict[str, Any]:
|
|
163
|
+
"""Build the team-readable proposal payload + live tally (Amendment A.6).
|
|
164
|
+
|
|
165
|
+
Pass ``voter_ids`` (the project's roster, computed once) when serializing a list so
|
|
166
|
+
the tally and the ``can_vote`` derivation reuse it instead of querying per proposal.
|
|
167
|
+
|
|
168
|
+
Per-voter privacy (ADR-0104 §2 + Amendment A.6/B.2, issue 1553): the individual
|
|
169
|
+
``votes`` list (which member voted which way) is a *team* signal — team-readable,
|
|
170
|
+
never project-wide. A requester who is not on the default-team roster (e.g. a
|
|
171
|
+
non-team project Admin/PM or a Viewer) is structurally outside the ladder for this
|
|
172
|
+
signal, so the per-voter choices are redacted for them. They still receive the
|
|
173
|
+
governance aggregate the policy-GET pending indicator needs — the tally counts,
|
|
174
|
+
``threshold``, ``eligible_count``, ``to_ceiling``, ``expires_at`` and their own
|
|
175
|
+
``your_vote`` (exactly the A.6 ``open_proposals`` shape) — but never the individual
|
|
176
|
+
choices. Team members receive the full per-voter list. This mirrors the write-side
|
|
177
|
+
``is_team_member`` gate on voting (Amendment A.2 / A.5: no management back-door).
|
|
178
|
+
"""
|
|
179
|
+
if voter_ids is None:
|
|
180
|
+
voter_ids = team_member_user_ids(proposal.project_id)
|
|
181
|
+
tally = proposal_tally(proposal, voter_ids=voter_ids)
|
|
182
|
+
votes = list(proposal.votes.all())
|
|
183
|
+
user = request.user
|
|
184
|
+
uid = user.pk if getattr(user, "is_authenticated", False) else None
|
|
185
|
+
your_vote: str | None = (
|
|
186
|
+
next((v.choice for v in votes if v.voter_id == uid), None) if uid is not None else None
|
|
187
|
+
)
|
|
188
|
+
# Team membership drives both can_vote and per-voter-detail visibility; derived
|
|
189
|
+
# from the roster set already in hand (no extra is_team_member query).
|
|
190
|
+
is_team_reader = uid is not None and uid in voter_ids
|
|
191
|
+
can_vote = proposal.status == CeilingRaiseStatus.OPEN and is_team_reader
|
|
192
|
+
# Per-voter choices stay team-scoped (issue 1553): redact for non-team readers so a
|
|
193
|
+
# project-level Admin/Viewer cannot see how each teammate voted, while keeping the
|
|
194
|
+
# aggregate tally that management legitimately reads for the pending indicator.
|
|
195
|
+
voter_detail = (
|
|
196
|
+
[{"voter": str(v.voter_id), "choice": v.choice, "created_at": v.created_at} for v in votes]
|
|
197
|
+
if is_team_reader
|
|
198
|
+
else []
|
|
199
|
+
)
|
|
200
|
+
return {
|
|
201
|
+
"id": str(proposal.id),
|
|
202
|
+
"signal": proposal.signal_key,
|
|
203
|
+
"from_ceiling": proposal.from_ceiling,
|
|
204
|
+
"to_ceiling": proposal.to_ceiling,
|
|
205
|
+
"status": proposal.status,
|
|
206
|
+
"proposed_by": str(proposal.proposed_by_id) if proposal.proposed_by_id else None,
|
|
207
|
+
"created_at": proposal.created_at,
|
|
208
|
+
"expires_at": proposal.expires_at,
|
|
209
|
+
"resolved_at": proposal.resolved_at,
|
|
210
|
+
"your_vote": your_vote,
|
|
211
|
+
"can_vote": can_vote,
|
|
212
|
+
"votes": voter_detail,
|
|
213
|
+
**tally,
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def _serialize_policy(
|
|
218
|
+
request: Request, project_id: Any, policy: ProjectSignalPrivacyPolicy
|
|
219
|
+
) -> dict[str, Any]:
|
|
220
|
+
# The reader band (TEAM for the team incl. the SM; TEAM_SM_PM for the PM) does
|
|
221
|
+
# not encode the SM *write* capability, so the write gate is checked directly:
|
|
222
|
+
# facilitator (SM facet) OR project Admin.
|
|
223
|
+
can_write = _is_facilitator_or_admin(request, project_id)
|
|
224
|
+
# One roster fetch drives both can_vote and every open-proposal tally below.
|
|
225
|
+
voter_ids = team_member_user_ids(project_id)
|
|
226
|
+
user = request.user
|
|
227
|
+
can_vote = getattr(user, "is_authenticated", False) and user.pk in voter_ids
|
|
228
|
+
open_proposals: dict[str, Any] = {
|
|
229
|
+
key: _serialize_proposal(proposal, request, voter_ids=voter_ids)
|
|
230
|
+
for key, proposal in live_open_proposals(project_id).items()
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
"signals": {key: policy.resolved(key) for key in SIGNAL_KEYS},
|
|
234
|
+
"requester_tier": requester_signal_tier(request, project_id),
|
|
235
|
+
# set-audience (immediate) and propose-raise share the facilitator/Admin gate;
|
|
236
|
+
# casting a ratification vote is the broader team-member gate.
|
|
237
|
+
"can_set_audience": can_write,
|
|
238
|
+
"can_raise_ceiling": can_write,
|
|
239
|
+
"can_vote": can_vote,
|
|
240
|
+
"open_proposals": open_proposals,
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class _SignalPrivacyBase(APIView):
|
|
245
|
+
"""Common project resolution + member gate for every signal-privacy endpoint."""
|
|
246
|
+
|
|
247
|
+
permission_classes = [IsAuthenticated, IsProjectMember, IsProjectNotArchived] # noqa: RUF012
|
|
248
|
+
|
|
249
|
+
def _project(self, project_pk: str) -> Project:
|
|
250
|
+
project = get_object_or_404(Project, pk=project_pk, is_deleted=False)
|
|
251
|
+
self.check_object_permissions(self.request, project)
|
|
252
|
+
return project
|
|
253
|
+
|
|
254
|
+
def _require_writer(self, project_id: Any) -> None:
|
|
255
|
+
if not _is_facilitator_or_admin(self.request, project_id):
|
|
256
|
+
raise PermissionDenied(
|
|
257
|
+
"Only the Scrum Master or a project Admin can change signal privacy."
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
# ---------------------------------------------------------------------------
|
|
262
|
+
# Endpoints
|
|
263
|
+
# ---------------------------------------------------------------------------
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
class SignalPrivacyPolicyView(_SignalPrivacyBase):
|
|
267
|
+
"""GET the posture; PATCH one signal's audience (within its ceiling)."""
|
|
268
|
+
|
|
269
|
+
@extend_schema(
|
|
270
|
+
summary="Read the team-signal privacy posture",
|
|
271
|
+
responses=SignalPrivacyPolicySerializer,
|
|
272
|
+
)
|
|
273
|
+
def get(self, request: Request, project_pk: str) -> Response:
|
|
274
|
+
project = self._project(project_pk)
|
|
275
|
+
policy = get_or_create_policy(project)
|
|
276
|
+
return Response(_serialize_policy(request, project.pk, policy))
|
|
277
|
+
|
|
278
|
+
@extend_schema(
|
|
279
|
+
summary="Set one signal's audience within its ceiling",
|
|
280
|
+
request=SetAudienceSerializer,
|
|
281
|
+
responses=SignalPrivacyPolicySerializer,
|
|
282
|
+
)
|
|
283
|
+
def patch(self, request: Request, project_pk: str) -> Response:
|
|
284
|
+
project = self._project(project_pk)
|
|
285
|
+
self._require_writer(project.pk)
|
|
286
|
+
body = SetAudienceSerializer(data=request.data)
|
|
287
|
+
body.is_valid(raise_exception=True)
|
|
288
|
+
policy = get_or_create_policy(project)
|
|
289
|
+
policy = set_signal_audience(
|
|
290
|
+
policy,
|
|
291
|
+
body.validated_data["signal"],
|
|
292
|
+
body.validated_data["audience"],
|
|
293
|
+
actor=request.user,
|
|
294
|
+
)
|
|
295
|
+
return Response(_serialize_policy(request, project.pk, policy))
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
class SignalPrivacyRaiseCeilingView(_SignalPrivacyBase):
|
|
299
|
+
"""POST — raise (→ opens a team-ratification proposal) or lower (immediate) a ceiling."""
|
|
300
|
+
|
|
301
|
+
@extend_schema(
|
|
302
|
+
summary="Raise (team-ratified) or lower (immediate) a signal's ceiling",
|
|
303
|
+
request=RaiseCeilingSerializer,
|
|
304
|
+
responses={
|
|
305
|
+
200: OpenApiResponse(SignalPrivacyPolicySerializer, description="Lower/no-op applied."),
|
|
306
|
+
202: OpenApiResponse(
|
|
307
|
+
CeilingProposalSerializer,
|
|
308
|
+
description="Raise opened a ratification proposal (or ratified immediately).",
|
|
309
|
+
),
|
|
310
|
+
409: OpenApiResponse(
|
|
311
|
+
description=(
|
|
312
|
+
"A ceiling-raise proposal is already open for this signal. Body: "
|
|
313
|
+
'``{code: "proposal_already_open", detail: ...}``.'
|
|
314
|
+
)
|
|
315
|
+
),
|
|
316
|
+
},
|
|
317
|
+
)
|
|
318
|
+
def post(self, request: Request, project_pk: str) -> Response:
|
|
319
|
+
project = self._project(project_pk)
|
|
320
|
+
self._require_writer(project.pk)
|
|
321
|
+
body = RaiseCeilingSerializer(data=request.data)
|
|
322
|
+
body.is_valid(raise_exception=True)
|
|
323
|
+
policy = get_or_create_policy(project)
|
|
324
|
+
try:
|
|
325
|
+
proposal = propose_or_apply_ceiling_change(
|
|
326
|
+
policy,
|
|
327
|
+
body.validated_data["signal"],
|
|
328
|
+
body.validated_data["ceiling"],
|
|
329
|
+
actor=request.user,
|
|
330
|
+
)
|
|
331
|
+
except CeilingProposalConflict as exc:
|
|
332
|
+
return Response(
|
|
333
|
+
{"code": exc.code, "detail": exc.detail}, status=status.HTTP_409_CONFLICT
|
|
334
|
+
)
|
|
335
|
+
if proposal is None:
|
|
336
|
+
# A lower / no-op applied immediately — return the refreshed policy.
|
|
337
|
+
policy = get_or_create_policy(project)
|
|
338
|
+
return Response(_serialize_policy(request, project.pk, policy))
|
|
339
|
+
# A raise opened a ratification proposal (still OPEN, or RATIFIED for a solo team).
|
|
340
|
+
return Response(_serialize_proposal(proposal, request), status=status.HTTP_202_ACCEPTED)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
class SignalPrivacyRatchetDownView(_SignalPrivacyBase):
|
|
344
|
+
"""POST — set every signal's audience to TEAM in one call (the SM panic button)."""
|
|
345
|
+
|
|
346
|
+
@extend_schema(
|
|
347
|
+
summary="Ratchet every signal's audience down to team-only",
|
|
348
|
+
request=None,
|
|
349
|
+
responses={200: OpenApiResponse(SignalPrivacyPolicySerializer)},
|
|
350
|
+
)
|
|
351
|
+
def post(self, request: Request, project_pk: str) -> Response:
|
|
352
|
+
project = self._project(project_pk)
|
|
353
|
+
self._require_writer(project.pk)
|
|
354
|
+
policy = get_or_create_policy(project)
|
|
355
|
+
policy = ratchet_down_to_team(policy, actor=request.user)
|
|
356
|
+
return Response(_serialize_policy(request, project.pk, policy), status=status.HTTP_200_OK)
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
class SignalCeilingProposalListView(_SignalPrivacyBase):
|
|
360
|
+
"""GET — list ceiling-raise proposals (open + recently resolved).
|
|
361
|
+
|
|
362
|
+
Every project member may read a proposal's governance aggregate (status, tally,
|
|
363
|
+
threshold, to_ceiling, expiry). The per-voter ``votes`` detail is team-scoped and
|
|
364
|
+
redacted for non-team readers by ``_serialize_proposal`` (ADR-0104 §2 / issue 1553).
|
|
365
|
+
"""
|
|
366
|
+
|
|
367
|
+
# How many resolved proposals to surface alongside the open ones — the audit tail.
|
|
368
|
+
_RESOLVED_LIMIT = 20
|
|
369
|
+
|
|
370
|
+
@extend_schema(
|
|
371
|
+
summary="List ceiling-raise ratification proposals (open + recent)",
|
|
372
|
+
responses=CeilingProposalSerializer(many=True),
|
|
373
|
+
)
|
|
374
|
+
def get(self, request: Request, project_pk: str) -> Response:
|
|
375
|
+
project = self._project(project_pk)
|
|
376
|
+
# Open first, then the most-recent resolved (audit tail). Prefetch votes so the
|
|
377
|
+
# per-proposal tally/serialize reads hit the cache (no N+1 over votes).
|
|
378
|
+
base = SignalCeilingRaiseProposal.objects.filter(project_id=project.pk).prefetch_related(
|
|
379
|
+
"votes"
|
|
380
|
+
)
|
|
381
|
+
open_qs = list(base.filter(status=CeilingRaiseStatus.OPEN).order_by("-created_at"))
|
|
382
|
+
resolved_qs = list(
|
|
383
|
+
base.exclude(status=CeilingRaiseStatus.OPEN).order_by("-resolved_at")[
|
|
384
|
+
: self._RESOLVED_LIMIT
|
|
385
|
+
]
|
|
386
|
+
)
|
|
387
|
+
# One roster fetch for the whole list (the roster is identical across a
|
|
388
|
+
# project's proposals) — no per-proposal team-membership query.
|
|
389
|
+
voter_ids = team_member_user_ids(project.pk)
|
|
390
|
+
payload = [
|
|
391
|
+
_serialize_proposal(p, request, voter_ids=voter_ids) for p in (*open_qs, *resolved_qs)
|
|
392
|
+
]
|
|
393
|
+
return Response(payload)
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
class SignalCeilingProposalVoteView(_SignalPrivacyBase):
|
|
397
|
+
"""POST — cast or change a vote on an open ceiling-raise proposal (team members only)."""
|
|
398
|
+
|
|
399
|
+
@extend_schema(
|
|
400
|
+
summary="Vote on a ceiling-raise proposal",
|
|
401
|
+
request=CeilingVoteRequestSerializer,
|
|
402
|
+
responses={
|
|
403
|
+
200: OpenApiResponse(CeilingProposalSerializer),
|
|
404
|
+
409: OpenApiResponse(
|
|
405
|
+
description=(
|
|
406
|
+
"The proposal is no longer open for voting (already resolved, or "
|
|
407
|
+
"lazily expired under the lock; body: "
|
|
408
|
+
'``{code: "proposal_closed", detail: ...}``), or — a narrow race '
|
|
409
|
+
"since this view already confirmed the proposal exists — it was "
|
|
410
|
+
"deleted between that check and the locked re-read (``{code: "
|
|
411
|
+
'"not_found", detail: ...}``).'
|
|
412
|
+
)
|
|
413
|
+
),
|
|
414
|
+
},
|
|
415
|
+
)
|
|
416
|
+
def post(self, request: Request, project_pk: str, proposal_pk: str) -> Response:
|
|
417
|
+
project = self._project(project_pk)
|
|
418
|
+
# IDOR-safe: the proposal must belong to this project (404 otherwise).
|
|
419
|
+
proposal = get_object_or_404(
|
|
420
|
+
SignalCeilingRaiseProposal, pk=proposal_pk, project_id=project.pk
|
|
421
|
+
)
|
|
422
|
+
# Only an active team member may vote — a non-team project Admin/PM cannot
|
|
423
|
+
# (Amendment A.2: the ratification is the team's, not management's).
|
|
424
|
+
if not is_team_member(request.user, project.pk):
|
|
425
|
+
raise PermissionDenied("Only a team member can vote on a signal-ceiling proposal.")
|
|
426
|
+
body = CeilingVoteRequestSerializer(data=request.data)
|
|
427
|
+
body.is_valid(raise_exception=True)
|
|
428
|
+
try:
|
|
429
|
+
proposal = cast_ceiling_vote(proposal.pk, request.user, body.validated_data["choice"])
|
|
430
|
+
except CeilingProposalConflict as exc:
|
|
431
|
+
return Response(
|
|
432
|
+
{"code": exc.code, "detail": exc.detail}, status=status.HTTP_409_CONFLICT
|
|
433
|
+
)
|
|
434
|
+
return Response(_serialize_proposal(proposal, request))
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
class SignalCeilingProposalWithdrawView(_SignalPrivacyBase):
|
|
438
|
+
"""POST — withdraw an open proposal (the proposer or a facilitator/Admin cancels it)."""
|
|
439
|
+
|
|
440
|
+
@extend_schema(
|
|
441
|
+
summary="Withdraw an open ceiling-raise proposal",
|
|
442
|
+
request=None,
|
|
443
|
+
responses={
|
|
444
|
+
200: OpenApiResponse(CeilingProposalSerializer),
|
|
445
|
+
409: OpenApiResponse(
|
|
446
|
+
description=(
|
|
447
|
+
"The proposal is no longer open (body: "
|
|
448
|
+
'``{code: "proposal_closed", detail: ...}``), or — a narrow race '
|
|
449
|
+
"since this view already confirmed the proposal exists — it was "
|
|
450
|
+
"deleted between that check and the locked re-read (``{code: "
|
|
451
|
+
'"not_found", detail: ...}``).'
|
|
452
|
+
)
|
|
453
|
+
),
|
|
454
|
+
},
|
|
455
|
+
)
|
|
456
|
+
def post(self, request: Request, project_pk: str, proposal_pk: str) -> Response:
|
|
457
|
+
project = self._project(project_pk)
|
|
458
|
+
proposal = get_object_or_404(
|
|
459
|
+
SignalCeilingRaiseProposal, pk=proposal_pk, project_id=project.pk
|
|
460
|
+
)
|
|
461
|
+
# Withdraw is the proposer's cancel; a facilitator/Admin may also clear it.
|
|
462
|
+
is_proposer = (
|
|
463
|
+
getattr(request.user, "is_authenticated", False)
|
|
464
|
+
and proposal.proposed_by_id == request.user.pk
|
|
465
|
+
)
|
|
466
|
+
if not (is_proposer or _is_facilitator_or_admin(request, project.pk)):
|
|
467
|
+
raise PermissionDenied(
|
|
468
|
+
"Only the proposer or a facilitator/Admin can withdraw this proposal."
|
|
469
|
+
)
|
|
470
|
+
try:
|
|
471
|
+
proposal = withdraw_ceiling_proposal(proposal.pk, request.user)
|
|
472
|
+
except CeilingProposalConflict as exc:
|
|
473
|
+
return Response(
|
|
474
|
+
{"code": exc.code, "detail": exc.detail}, status=status.HTTP_409_CONFLICT
|
|
475
|
+
)
|
|
476
|
+
return Response(_serialize_proposal(proposal, request))
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Django signals for the projects app.
|
|
2
|
+
|
|
3
|
+
The ``risk_changed`` signal is the OSS extension point for Enterprise
|
|
4
|
+
portfolio risk rollup. Enterprise receivers connect via their own
|
|
5
|
+
AppConfig.ready() without modifying OSS code.
|
|
6
|
+
|
|
7
|
+
Usage (Enterprise side, never imported by OSS)::
|
|
8
|
+
|
|
9
|
+
# trueppm_enterprise/portfolio_risks/apps.py
|
|
10
|
+
def ready(self):
|
|
11
|
+
from trueppm_api.apps.projects.signals import risk_changed
|
|
12
|
+
risk_changed.connect(portfolio_risk_rollup_receiver)
|
|
13
|
+
|
|
14
|
+
.. warning::
|
|
15
|
+
The signal is emitted synchronously inside the database transaction.
|
|
16
|
+
Enterprise receivers that perform I/O (HTTP calls, cross-DB writes) **must**
|
|
17
|
+
defer those side-effects with ``transaction.on_commit()`` to avoid blocking
|
|
18
|
+
the transaction or writing to external systems on a rolled-back save.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import django.dispatch
|
|
24
|
+
|
|
25
|
+
# Sent after a Risk is saved or soft-deleted.
|
|
26
|
+
#
|
|
27
|
+
# Keyword arguments:
|
|
28
|
+
# sender — the Risk class
|
|
29
|
+
# risk — the Risk instance (post-save state)
|
|
30
|
+
# action — "saved" | "deleted"
|
|
31
|
+
#
|
|
32
|
+
# Only emitted when probability, impact, or status changes on save.
|
|
33
|
+
# For soft-deletes, ``action="deleted"`` is sent by Risk.soft_delete()
|
|
34
|
+
# and the save() emission is suppressed (is_deleted is already True).
|
|
35
|
+
risk_changed = django.dispatch.Signal()
|
|
36
|
+
|
|
37
|
+
# Sent after a Task's status field changes (board card drag or direct API update).
|
|
38
|
+
#
|
|
39
|
+
# Keyword arguments:
|
|
40
|
+
# sender — the Task class
|
|
41
|
+
# task — the Task instance (post-save state)
|
|
42
|
+
# old_status — previous TaskStatus value (None on first save / INSERT)
|
|
43
|
+
# new_status — new TaskStatus value
|
|
44
|
+
#
|
|
45
|
+
# Only emitted when the status value actually changes. CPM bulk_update bypasses
|
|
46
|
+
# Task.save() so this signal never fires for scheduling engine writes.
|
|
47
|
+
# Enterprise receivers must use transaction.on_commit() for any I/O side-effects.
|
|
48
|
+
task_status_changed = django.dispatch.Signal()
|
|
49
|
+
|
|
50
|
+
# Sent after a SprintScopeChange row is created (ADR-0060 #308, generalized in
|
|
51
|
+
# ADR-0101 §5 / ADR-0102). Fires when *any* task is linked to a task's ACTIVE
|
|
52
|
+
# sprint mid-sprint — the original subtask-spawn path, plus the direct
|
|
53
|
+
# "assign existing task to the active sprint" path. The row enters the pending
|
|
54
|
+
# state (ADR-0102): excluded from commitment/burndown until a team member with
|
|
55
|
+
# the sprint-lifecycle gate accepts or rejects it.
|
|
56
|
+
#
|
|
57
|
+
# Keyword arguments:
|
|
58
|
+
# sender — the SprintScopeChange class
|
|
59
|
+
# scope_change — the SprintScopeChange instance (post-save)
|
|
60
|
+
# task — the Task whose sprint membership changed (the injected item)
|
|
61
|
+
#
|
|
62
|
+
# OSS only emits; Enterprise registers receivers via AppConfig.ready().
|
|
63
|
+
# Receivers that perform I/O must use transaction.on_commit() to avoid
|
|
64
|
+
# blocking the transaction or writing on rollback.
|
|
65
|
+
#
|
|
66
|
+
# ADR-0102 §3 invariant: this signal is *notify-only*. No receiver may flip a
|
|
67
|
+
# SprintScopeChange.status away from PENDING — accept/reject are exclusively the
|
|
68
|
+
# human-invoked service functions (accept_scope_change / reject_scope_change).
|
|
69
|
+
sprint_scope_changed = django.dispatch.Signal()
|
|
70
|
+
|
|
71
|
+
# Sent after a bound milestone is reforecast (ADR-0106 §6, #860). This is the
|
|
72
|
+
# one OSS read-only seam the Enterprise cross-team forecast mirrors (#140/#141/
|
|
73
|
+
# #142, 1.0) register against. OSS itself never aggregates across teams.
|
|
74
|
+
#
|
|
75
|
+
# Keyword arguments (band + dates ONLY — never the velocity series, never any
|
|
76
|
+
# per-contributor data):
|
|
77
|
+
# sender — the ForecastSnapshot class
|
|
78
|
+
# project_id — the project UUID (str)
|
|
79
|
+
# milestone_id — the bound milestone Task UUID (str)
|
|
80
|
+
# cpm_finish — ISO date str | None (the deterministic CPM spine)
|
|
81
|
+
# p50 — ISO date str | None
|
|
82
|
+
# p80 — ISO date str | None
|
|
83
|
+
# confidence — "high" | "medium" | "low"
|
|
84
|
+
# unmodeled_dependency — bool (§4 cheap predecessor heuristic)
|
|
85
|
+
#
|
|
86
|
+
# Privacy (ADR-0106 §6): the signal fires for OSS's own forecast-history needs
|
|
87
|
+
# unconditionally, but the *cross-team-eligible* projection of it is supplied
|
|
88
|
+
# only through the consent-respecting provider in the Unified Team-Signal Privacy
|
|
89
|
+
# Model — the Enterprise receiver must consult the consent record; OSS never does
|
|
90
|
+
# the aggregation. Receivers performing I/O must use transaction.on_commit().
|
|
91
|
+
milestone_forecast_recomputed = django.dispatch.Signal()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# Emitted when a team changes how far one of its signals may travel — an audience
|
|
95
|
+
# move or a ceiling raise/lower on ProjectSignalPrivacyPolicy (ADR-0104 §3).
|
|
96
|
+
#
|
|
97
|
+
# Keyword arguments:
|
|
98
|
+
# sender — the ProjectSignalPrivacyPolicy class
|
|
99
|
+
# project_id — the project whose policy changed
|
|
100
|
+
# signal_key — the signal that moved ("velocity" / "throughput_rollup" / "pulse")
|
|
101
|
+
# change — "audience" or "ceiling"
|
|
102
|
+
# old — prior SignalAudience value
|
|
103
|
+
# new — new SignalAudience value
|
|
104
|
+
#
|
|
105
|
+
# Supply-only extension point: OSS only emits. Enterprise registers a receiver in
|
|
106
|
+
# AppConfig.ready() to invalidate its cross-team rollup cache / capture the upward-
|
|
107
|
+
# share decision in its immutable audit. Receivers that perform I/O must use
|
|
108
|
+
# transaction.on_commit(). No receiver may write signal_visibility — the only
|
|
109
|
+
# writers are the human-invoked, member-gated services (set_signal_audience /
|
|
110
|
+
# raise_signal_ceiling / ratchet_down_to_team).
|
|
111
|
+
team_signal_consent_changed = django.dispatch.Signal()
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# Emitted on every status transition of a ceiling-raise ratification proposal
|
|
115
|
+
# (ADR-0104 Amendment A / #930) — opened, ratified, rejected, expired, superseded.
|
|
116
|
+
#
|
|
117
|
+
# Keyword arguments:
|
|
118
|
+
# sender — the SignalCeilingRaiseProposal class
|
|
119
|
+
# project_id — the project whose signal ceiling is under ratification
|
|
120
|
+
# signal_key — the signal being raised ("velocity" / "throughput_rollup" / ...)
|
|
121
|
+
# proposal_id — the proposal's UUID
|
|
122
|
+
# status — the new CeilingRaiseStatus value
|
|
123
|
+
#
|
|
124
|
+
# Supply-only extension point, same contract as team_signal_consent_changed:
|
|
125
|
+
# projects only emits; consumers register a receiver in AppConfig.ready(). Since
|
|
126
|
+
# #1275 the OSS notifications app consumes it (notifications/receivers.py →
|
|
127
|
+
# in-app inbox row for eligible voters, ADR-0104 Amendment B); Enterprise may
|
|
128
|
+
# register its own receiver to capture the ratification lifecycle in its immutable
|
|
129
|
+
# audit. The signal is already deferred via transaction.on_commit (see
|
|
130
|
+
# _emit_proposal_changed), so receivers run post-commit and read committed state.
|
|
131
|
+
# Email stays opt-in OFF (Priya's hard-NO is on un-opted email, not the inbox).
|
|
132
|
+
team_signal_ceiling_proposal_changed = django.dispatch.Signal()
|