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,533 @@
|
|
|
1
|
+
"""Cross-process OTLP export-health record for the live Telemetry card (ADR-0601, #2109).
|
|
2
|
+
|
|
3
|
+
The read-only System Health "Telemetry" card (#2022/#2110) reports export
|
|
4
|
+
*configuration* but no live numbers, because the SDK export pipeline
|
|
5
|
+
(``BatchSpanProcessor`` / ``PeriodicExportingMetricReader``) discards the
|
|
6
|
+
``SpanExportResult`` / ``MetricExportResult`` it gets back. This module recovers
|
|
7
|
+
that signal without touching the export path's behaviour.
|
|
8
|
+
|
|
9
|
+
Two facts drive the design (see ADR-0601 for the full rationale):
|
|
10
|
+
|
|
11
|
+
1. **The web pod that serves ``/settings/health`` exports almost nothing.** The
|
|
12
|
+
real span/metric volume is on the Celery worker and beat pods, so a count read
|
|
13
|
+
from the web process would be a misleadingly-small number. The card therefore
|
|
14
|
+
needs a *cluster* view, not a per-process one — TruePPM's honesty-to-operator
|
|
15
|
+
rule (no fabricated numbers) makes a wrong-scope figure worse than none.
|
|
16
|
+
2. **Valkey is already a hard dependency** (Celery ``/0``, channels ``/1``, cache +
|
|
17
|
+
throttle counters ``/2``). Each exporting pod self-reports its per-signal health
|
|
18
|
+
into ``/2`` under the ``otel:exphealth:`` prefix; :func:`read_export_health`
|
|
19
|
+
aggregates the live pods into the cluster figure the card shows.
|
|
20
|
+
|
|
21
|
+
Every write is **best-effort and non-fatal**: the recorder wrappers always
|
|
22
|
+
propagate the wrapped exporter's own return value and never raise into the SDK's
|
|
23
|
+
export loop, and a Valkey outage degrades the card to its config-only posture
|
|
24
|
+
rather than fabricating a number. This mirrors the discipline the observable
|
|
25
|
+
gauges already follow — telemetry must never destabilize the process it observes.
|
|
26
|
+
|
|
27
|
+
Eviction note: these keys carry TTLs and share the ``/2`` logical DB with the
|
|
28
|
+
Django cache and the throttle counters, which already require the Valkey
|
|
29
|
+
``maxmemory-policy`` to be ``noeviction`` (the default). An evicted record can only
|
|
30
|
+
degrade to the neutral ``never`` state — never the red ``stalled`` alarm, which
|
|
31
|
+
requires a surviving ``last_success_at`` — so the card stays honest even under a
|
|
32
|
+
misconfigured eviction policy.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
from __future__ import annotations
|
|
36
|
+
|
|
37
|
+
import logging
|
|
38
|
+
import os
|
|
39
|
+
import socket
|
|
40
|
+
import time
|
|
41
|
+
from datetime import UTC, datetime
|
|
42
|
+
from typing import TYPE_CHECKING, Any
|
|
43
|
+
|
|
44
|
+
import redis
|
|
45
|
+
from django.conf import settings
|
|
46
|
+
from opentelemetry.sdk.metrics.export import MetricExporter, MetricExportResult
|
|
47
|
+
from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
|
|
48
|
+
|
|
49
|
+
from trueppm_api.core import valkey
|
|
50
|
+
|
|
51
|
+
if TYPE_CHECKING:
|
|
52
|
+
from collections.abc import Sequence
|
|
53
|
+
|
|
54
|
+
from opentelemetry.sdk.metrics.export import MetricsData
|
|
55
|
+
from opentelemetry.sdk.trace import ReadableSpan
|
|
56
|
+
|
|
57
|
+
logger = logging.getLogger(__name__)
|
|
58
|
+
|
|
59
|
+
# Valkey key prefix; ``/2`` is the throttle/cache logical DB (see module docstring).
|
|
60
|
+
_KEY_PREFIX = "otel:exphealth:"
|
|
61
|
+
|
|
62
|
+
# --- Threshold DEFAULTS (seconds) ------------------------------------------------
|
|
63
|
+
# Each is operator-overridable via a settings/env knob (see the accessors below);
|
|
64
|
+
# the constant is the default the accessor falls back to. Kept as named module
|
|
65
|
+
# constants so tests and the accessors share one source of truth.
|
|
66
|
+
#
|
|
67
|
+
# A pod's record is considered live for STALENESS_SECONDS after its last export
|
|
68
|
+
# attempt (success OR failure — both refresh the record); a pod that stops exporting
|
|
69
|
+
# entirely expires out of the aggregate after this and the signal reads `never`.
|
|
70
|
+
# STALENESS must stay wider than HEALTHY_WITHIN so a signal that has gone quiet or
|
|
71
|
+
# overdue is still *observed* (as idle / stalled) before it expires — a shorter TTL
|
|
72
|
+
# would mask a stalled signal as a premature `never` (enforced by _warn_if_misordered).
|
|
73
|
+
STALENESS_SECONDS = 600
|
|
74
|
+
# A success within HEALTHY_WITHIN_SECONDS ⇒ healthy. Beyond it (but still a live
|
|
75
|
+
# record) ⇒ stalled for metrics (fixed-cadence, so overdue is authoritative) or idle
|
|
76
|
+
# for traces (volume-driven, so quiet is legitimate — never a red alarm). Sized a few
|
|
77
|
+
# multiples of the default 60s metric export interval so cadence jitter never flaps.
|
|
78
|
+
HEALTHY_WITHIN_SECONDS = 150
|
|
79
|
+
# The rolling window the exported-item counts cover ("N spans / <window>s"). Reported
|
|
80
|
+
# to the FE as ``window_seconds`` so the denominator is never hard-coded in the UI —
|
|
81
|
+
# changing it re-labels the strip automatically ("last 120s").
|
|
82
|
+
WINDOW_SECONDS = 60
|
|
83
|
+
|
|
84
|
+
# Signal identifiers used in keys and payloads.
|
|
85
|
+
SIGNAL_TRACES = "traces"
|
|
86
|
+
SIGNAL_METRICS = "metrics"
|
|
87
|
+
|
|
88
|
+
_pool: redis.ConnectionPool | None = None
|
|
89
|
+
# One-shot guard so the misordered-threshold warning is logged at most once/process.
|
|
90
|
+
_misorder_warned = False
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _staleness_seconds() -> int:
|
|
94
|
+
"""Per-pod record TTL / live window (``TRUEPPM_OTEL_EXPORT_HEALTH_STALENESS_SECONDS``)."""
|
|
95
|
+
return int(getattr(settings, "TRUEPPM_OTEL_EXPORT_HEALTH_STALENESS_SECONDS", STALENESS_SECONDS))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _healthy_within_seconds() -> int:
|
|
99
|
+
"""Healthy-success window (``TRUEPPM_OTEL_EXPORT_HEALTH_HEALTHY_WITHIN_SECONDS``)."""
|
|
100
|
+
return int(
|
|
101
|
+
getattr(
|
|
102
|
+
settings, "TRUEPPM_OTEL_EXPORT_HEALTH_HEALTHY_WITHIN_SECONDS", HEALTHY_WITHIN_SECONDS
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _window_seconds() -> int:
|
|
108
|
+
"""Rolling item-count window (``TRUEPPM_OTEL_EXPORT_HEALTH_WINDOW_SECONDS``)."""
|
|
109
|
+
return int(getattr(settings, "TRUEPPM_OTEL_EXPORT_HEALTH_WINDOW_SECONDS", WINDOW_SECONDS))
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _warn_if_misordered() -> None:
|
|
113
|
+
"""Warn once if the operator configured STALENESS <= HEALTHY_WITHIN.
|
|
114
|
+
|
|
115
|
+
That ordering is a correctness invariant: a record must stay live long enough to
|
|
116
|
+
be *seen* as stalled/idle before it expires, or an overdue signal is masked as a
|
|
117
|
+
premature `never`. We do not clamp (the operator owns their config) — we surface
|
|
118
|
+
the misconfiguration and degrade honestly to the neutral state.
|
|
119
|
+
"""
|
|
120
|
+
global _misorder_warned
|
|
121
|
+
if _misorder_warned:
|
|
122
|
+
return
|
|
123
|
+
if _staleness_seconds() <= _healthy_within_seconds():
|
|
124
|
+
_misorder_warned = True
|
|
125
|
+
logger.warning(
|
|
126
|
+
"TRUEPPM_OTEL_EXPORT_HEALTH_STALENESS_SECONDS (%s) must exceed "
|
|
127
|
+
"TRUEPPM_OTEL_EXPORT_HEALTH_HEALTHY_WITHIN_SECONDS (%s); with this ordering an "
|
|
128
|
+
"overdue traces/metrics signal will read as 'never' instead of idle/stalled.",
|
|
129
|
+
_staleness_seconds(),
|
|
130
|
+
_healthy_within_seconds(),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _client() -> redis.Redis:
|
|
135
|
+
"""Return a redis client on the throttle/cache logical DB (``/2``).
|
|
136
|
+
|
|
137
|
+
Reuses the throttle module's connection-pool idiom (``ConnectionPool.from_url``)
|
|
138
|
+
so export-health writes share one bounded pool rather than opening a socket per
|
|
139
|
+
export.
|
|
140
|
+
"""
|
|
141
|
+
global _pool
|
|
142
|
+
if _pool is None:
|
|
143
|
+
_pool = valkey.pool(
|
|
144
|
+
valkey.DB_CACHE, # the throttle/cache DB (noeviction)
|
|
145
|
+
decode_responses=True,
|
|
146
|
+
# Bound both the aggregate read (on the polled /settings/health request
|
|
147
|
+
# thread) and the recorder write (on the SDK export thread) so a silently
|
|
148
|
+
# hung Valkey — a TCP blackhole, which a plain refused/reset would not be —
|
|
149
|
+
# can never wedge either. A timeout raises redis.TimeoutError (a
|
|
150
|
+
# RedisError), which the reader catches (→ available:false) and the
|
|
151
|
+
# recorder swallows.
|
|
152
|
+
socket_timeout=2,
|
|
153
|
+
socket_connect_timeout=2,
|
|
154
|
+
)
|
|
155
|
+
return redis.Redis(connection_pool=_pool)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _pod_id() -> str:
|
|
159
|
+
"""Stable per-pod identity for the record.
|
|
160
|
+
|
|
161
|
+
``TRUEPPM_POD_NAME`` (K8s downward API) when set, else
|
|
162
|
+
``socket.gethostname():<pid>`` — on Kubernetes the hostname *is* the pod name,
|
|
163
|
+
so the fallback is a stable, no-config default.
|
|
164
|
+
"""
|
|
165
|
+
name = str(getattr(settings, "TRUEPPM_POD_NAME", "") or "").strip()
|
|
166
|
+
if name:
|
|
167
|
+
return name
|
|
168
|
+
return f"{socket.gethostname()}:{os.getpid()}"
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _iso(epoch: float | None) -> str | None:
|
|
172
|
+
"""Render an epoch timestamp as ``…Z`` ISO-8601, or ``None``."""
|
|
173
|
+
if epoch is None:
|
|
174
|
+
return None
|
|
175
|
+
return datetime.fromtimestamp(epoch, tz=UTC).isoformat().replace("+00:00", "Z")
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
# ---------------------------------------------------------------------------
|
|
179
|
+
# Recorder — one instance per signal, written only from the SDK export thread.
|
|
180
|
+
# ---------------------------------------------------------------------------
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class ExportHealthRecorder:
|
|
184
|
+
"""Publishes one signal's export health for this pod into Valkey.
|
|
185
|
+
|
|
186
|
+
A single instance per signal (traces / metrics). Its ``record_*`` methods are
|
|
187
|
+
called only from that signal's dedicated SDK background export thread
|
|
188
|
+
(``BatchSpanProcessor`` / ``PeriodicExportingMetricReader`` each own one), so
|
|
189
|
+
the in-process ring buffer has exactly one writer and needs no lock. Every
|
|
190
|
+
Valkey write is wrapped so a store failure is logged at debug and swallowed —
|
|
191
|
+
it must never propagate into the export loop.
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
def __init__(self, signal: str, service_name: str) -> None:
|
|
195
|
+
self._signal = signal
|
|
196
|
+
self._service = service_name
|
|
197
|
+
self._pod = _pod_id()
|
|
198
|
+
# epoch-second -> items exported in that second, pruned to the trailing
|
|
199
|
+
# window. Sums to the pod's own "items in the last <window>s".
|
|
200
|
+
self._buckets: dict[int, int] = {}
|
|
201
|
+
|
|
202
|
+
def _items_in_window(self, now: int) -> int:
|
|
203
|
+
"""Sum exported items over the trailing window, pruning old slots."""
|
|
204
|
+
cutoff = now - _window_seconds()
|
|
205
|
+
for sec in [s for s in self._buckets if s <= cutoff]:
|
|
206
|
+
del self._buckets[sec]
|
|
207
|
+
return sum(self._buckets.values())
|
|
208
|
+
|
|
209
|
+
def record_success(self, item_count: int) -> None:
|
|
210
|
+
"""Record a successful export of ``item_count`` items (spans / data points)."""
|
|
211
|
+
try:
|
|
212
|
+
now = int(time.time())
|
|
213
|
+
self._buckets[now] = self._buckets.get(now, 0) + max(item_count, 0)
|
|
214
|
+
self._write(
|
|
215
|
+
{
|
|
216
|
+
"last_success_at": now,
|
|
217
|
+
"items_window": self._items_in_window(now),
|
|
218
|
+
"service": self._service,
|
|
219
|
+
"exporting": "1",
|
|
220
|
+
},
|
|
221
|
+
now,
|
|
222
|
+
)
|
|
223
|
+
except Exception:
|
|
224
|
+
logger.debug("export-health: success record failed", exc_info=True)
|
|
225
|
+
|
|
226
|
+
def record_failure(self, error: str) -> None:
|
|
227
|
+
"""Record a failed export, keeping a truncated error string for the card."""
|
|
228
|
+
try:
|
|
229
|
+
now = int(time.time())
|
|
230
|
+
# last_success_at is intentionally NOT cleared: the reader compares
|
|
231
|
+
# last_error_at vs last_success_at to decide failing-vs-recovered.
|
|
232
|
+
self._write(
|
|
233
|
+
{
|
|
234
|
+
"last_error_at": now,
|
|
235
|
+
"last_error": (error or "export failed")[:200],
|
|
236
|
+
"service": self._service,
|
|
237
|
+
"exporting": "1",
|
|
238
|
+
},
|
|
239
|
+
now,
|
|
240
|
+
)
|
|
241
|
+
except Exception:
|
|
242
|
+
logger.debug("export-health: failure record failed", exc_info=True)
|
|
243
|
+
|
|
244
|
+
def _write(self, fields: dict[str, Any], now: int) -> None:
|
|
245
|
+
"""Atomically upsert this pod's hash + refresh the live-pod index."""
|
|
246
|
+
client = _client()
|
|
247
|
+
pod_key = f"{_KEY_PREFIX}pod:{self._signal}:{self._pod}"
|
|
248
|
+
idx_key = f"{_KEY_PREFIX}idx:{self._signal}"
|
|
249
|
+
staleness = _staleness_seconds()
|
|
250
|
+
pipe = client.pipeline()
|
|
251
|
+
pipe.hset(pod_key, mapping=fields)
|
|
252
|
+
pipe.expire(pod_key, staleness)
|
|
253
|
+
pipe.zadd(idx_key, {self._pod: now})
|
|
254
|
+
# The index outlives a single staleness window so a burst of dead members
|
|
255
|
+
# is trimmed lazily by the reader rather than expiring the whole index.
|
|
256
|
+
pipe.expire(idx_key, staleness * 4)
|
|
257
|
+
pipe.execute()
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
# ---------------------------------------------------------------------------
|
|
261
|
+
# Wrapping exporters — compose the real OTLP exporter, record its result.
|
|
262
|
+
# ---------------------------------------------------------------------------
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def _safe_record(fn: Any, *args: Any) -> None:
|
|
266
|
+
"""Run a recorder call, swallowing any error.
|
|
267
|
+
|
|
268
|
+
Belt-and-braces around the recorder's own internal guard: the wrapper's
|
|
269
|
+
non-fatal contract (a recording side effect can never raise into the SDK
|
|
270
|
+
export loop) must hold regardless of the recorder implementation.
|
|
271
|
+
"""
|
|
272
|
+
try:
|
|
273
|
+
fn(*args)
|
|
274
|
+
except Exception:
|
|
275
|
+
logger.debug("export-health: recorder call failed", exc_info=True)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
class RecordingSpanExporter(SpanExporter):
|
|
279
|
+
"""Delegating ``SpanExporter`` that records each export's outcome (ADR-0601 §1).
|
|
280
|
+
|
|
281
|
+
Inserted between ``build_span_exporter()`` and the ``BatchSpanProcessor``.
|
|
282
|
+
``export`` always returns the wrapped exporter's own ``SpanExportResult`` and
|
|
283
|
+
re-raises any exception it raises (preserving the SDK's exact behaviour); the
|
|
284
|
+
recording is a swallowed side effect, so this wrapper can never change, slow,
|
|
285
|
+
or break export.
|
|
286
|
+
"""
|
|
287
|
+
|
|
288
|
+
def __init__(self, wrapped: SpanExporter, recorder: ExportHealthRecorder) -> None:
|
|
289
|
+
self._wrapped = wrapped
|
|
290
|
+
self._recorder = recorder
|
|
291
|
+
|
|
292
|
+
def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult:
|
|
293
|
+
try:
|
|
294
|
+
result = self._wrapped.export(spans)
|
|
295
|
+
except Exception as exc: # record then re-raise — behaviour unchanged
|
|
296
|
+
# Record only the exception TYPE, never str(exc): the routine network/auth
|
|
297
|
+
# failures are already swallowed by the exporter and return FAILURE (below),
|
|
298
|
+
# so this branch is for unexpected exceptions — recording the type keeps the
|
|
299
|
+
# "the OTLP bearer token never reaches the browser" invariant structural,
|
|
300
|
+
# not dependent on upstream exporters continuing to hide it. Full detail
|
|
301
|
+
# goes to the server log.
|
|
302
|
+
logger.debug("export-health: span export raised", exc_info=True)
|
|
303
|
+
_safe_record(self._recorder.record_failure, type(exc).__name__)
|
|
304
|
+
raise
|
|
305
|
+
if result == SpanExportResult.SUCCESS:
|
|
306
|
+
_safe_record(self._recorder.record_success, len(spans))
|
|
307
|
+
else:
|
|
308
|
+
_safe_record(self._recorder.record_failure, "span exporter returned FAILURE")
|
|
309
|
+
return result
|
|
310
|
+
|
|
311
|
+
def force_flush(self, timeout_millis: int = 30_000) -> bool:
|
|
312
|
+
return self._wrapped.force_flush(timeout_millis)
|
|
313
|
+
|
|
314
|
+
def shutdown(self) -> None:
|
|
315
|
+
self._wrapped.shutdown()
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
class RecordingMetricExporter(MetricExporter):
|
|
319
|
+
"""Delegating ``MetricExporter`` that records each export's outcome (ADR-0601 §1).
|
|
320
|
+
|
|
321
|
+
Mirrors :class:`RecordingSpanExporter` for metrics. The base ``MetricExporter``
|
|
322
|
+
carries the temporality/aggregation preferences the ``PeriodicExportingMetricReader``
|
|
323
|
+
reads off the exporter, so they are copied from the wrapped exporter to stay
|
|
324
|
+
transparent to the reader.
|
|
325
|
+
"""
|
|
326
|
+
|
|
327
|
+
def __init__(self, wrapped: MetricExporter, recorder: ExportHealthRecorder) -> None:
|
|
328
|
+
self._wrapped = wrapped
|
|
329
|
+
self._recorder = recorder
|
|
330
|
+
super().__init__(
|
|
331
|
+
preferred_temporality=getattr(wrapped, "_preferred_temporality", None),
|
|
332
|
+
preferred_aggregation=getattr(wrapped, "_preferred_aggregation", None),
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
def export(
|
|
336
|
+
self, metrics_data: MetricsData, timeout_millis: float = 10_000, **kwargs: Any
|
|
337
|
+
) -> MetricExportResult:
|
|
338
|
+
try:
|
|
339
|
+
result = self._wrapped.export(metrics_data, timeout_millis=timeout_millis, **kwargs)
|
|
340
|
+
except Exception as exc: # record then re-raise — behaviour unchanged
|
|
341
|
+
# Type only, never str(exc) — see RecordingSpanExporter.export for why.
|
|
342
|
+
logger.debug("export-health: metric export raised", exc_info=True)
|
|
343
|
+
_safe_record(self._recorder.record_failure, type(exc).__name__)
|
|
344
|
+
raise
|
|
345
|
+
if result == MetricExportResult.SUCCESS:
|
|
346
|
+
_safe_record(self._recorder.record_success, _count_data_points(metrics_data))
|
|
347
|
+
else:
|
|
348
|
+
_safe_record(self._recorder.record_failure, "metric exporter returned FAILURE")
|
|
349
|
+
return result
|
|
350
|
+
|
|
351
|
+
def force_flush(self, timeout_millis: float = 10_000) -> bool:
|
|
352
|
+
return self._wrapped.force_flush(timeout_millis)
|
|
353
|
+
|
|
354
|
+
def shutdown(self, timeout_millis: float = 30_000, **kwargs: Any) -> None:
|
|
355
|
+
self._wrapped.shutdown(timeout_millis=timeout_millis, **kwargs)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def _count_data_points(metrics_data: MetricsData) -> int:
|
|
359
|
+
"""Count metric data points in a ``MetricsData`` batch (bounded, once per interval)."""
|
|
360
|
+
total = 0
|
|
361
|
+
try:
|
|
362
|
+
for resource_metrics in metrics_data.resource_metrics:
|
|
363
|
+
for scope_metrics in resource_metrics.scope_metrics:
|
|
364
|
+
for metric in scope_metrics.metrics:
|
|
365
|
+
total += len(metric.data.data_points)
|
|
366
|
+
except Exception:
|
|
367
|
+
return total
|
|
368
|
+
return total
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
# ---------------------------------------------------------------------------
|
|
372
|
+
# Reader — aggregate live pods into the cluster view for _telemetry().
|
|
373
|
+
# ---------------------------------------------------------------------------
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def read_export_health(
|
|
377
|
+
*,
|
|
378
|
+
traces_enabled: bool,
|
|
379
|
+
metrics_enabled: bool,
|
|
380
|
+
now_epoch: float | None = None,
|
|
381
|
+
) -> dict[str, Any]:
|
|
382
|
+
"""Aggregate live pods into the ``live`` block consumed by the Telemetry card.
|
|
383
|
+
|
|
384
|
+
Returns ``{"available": False}`` when the recorder is disabled by config or the
|
|
385
|
+
store is unreachable — the FE then keeps the config-only posture rather than
|
|
386
|
+
showing a fabricated number. Otherwise returns the additive ``live`` block
|
|
387
|
+
(ADR-0601 §3): ``window_seconds``, ``pods_reporting``, and a per-signal block
|
|
388
|
+
carrying the server-computed ``state`` verdict, cluster ``last_success`` /
|
|
389
|
+
``last_error``, and the summed item count.
|
|
390
|
+
|
|
391
|
+
Args:
|
|
392
|
+
traces_enabled: Whether the traces signal is enabled by config.
|
|
393
|
+
metrics_enabled: Whether the metrics signal is enabled by config.
|
|
394
|
+
now_epoch: Reference time (defaults to ``time.time()``); injectable for tests.
|
|
395
|
+
"""
|
|
396
|
+
if not getattr(settings, "TRUEPPM_OTEL_EXPORT_HEALTH_ENABLED", True):
|
|
397
|
+
return {"available": False}
|
|
398
|
+
_warn_if_misordered()
|
|
399
|
+
now = now_epoch if now_epoch is not None else time.time()
|
|
400
|
+
try:
|
|
401
|
+
client = _client()
|
|
402
|
+
traces, trace_pods = _read_signal(client, SIGNAL_TRACES, enabled=traces_enabled, now=now)
|
|
403
|
+
metrics, metric_pods = _read_signal(
|
|
404
|
+
client, SIGNAL_METRICS, enabled=metrics_enabled, now=now
|
|
405
|
+
)
|
|
406
|
+
except redis.RedisError:
|
|
407
|
+
logger.debug("export-health: aggregate read failed", exc_info=True)
|
|
408
|
+
return {"available": False}
|
|
409
|
+
return {
|
|
410
|
+
"available": True,
|
|
411
|
+
"window_seconds": _window_seconds(),
|
|
412
|
+
"pods_reporting": len(trace_pods | metric_pods),
|
|
413
|
+
"traces": traces,
|
|
414
|
+
"metrics": metrics,
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def _fold_pod_hashes(
|
|
419
|
+
hashes: list[dict[str, str]],
|
|
420
|
+
) -> tuple[float | None, float | None, str | None, int]:
|
|
421
|
+
"""Fold the live pods' hashes into cluster-wide aggregates.
|
|
422
|
+
|
|
423
|
+
Returns ``(last_success, last_error_at, last_error, items)`` — the most recent
|
|
424
|
+
success/error timestamps across all reporting pods (with the error message that
|
|
425
|
+
accompanied the newest error), and the summed windowed item count. Empty/evicted
|
|
426
|
+
hashes are skipped so an expired record cannot reset an otherwise-live aggregate.
|
|
427
|
+
"""
|
|
428
|
+
last_success: float | None = None
|
|
429
|
+
last_error_at: float | None = None
|
|
430
|
+
last_error: str | None = None
|
|
431
|
+
items = 0
|
|
432
|
+
for h in hashes:
|
|
433
|
+
if not h:
|
|
434
|
+
continue
|
|
435
|
+
success_at = _as_float(h.get("last_success_at"))
|
|
436
|
+
if success_at is not None and (last_success is None or success_at > last_success):
|
|
437
|
+
last_success = success_at
|
|
438
|
+
error_at = _as_float(h.get("last_error_at"))
|
|
439
|
+
if error_at is not None and (last_error_at is None or error_at > last_error_at):
|
|
440
|
+
last_error_at = error_at
|
|
441
|
+
last_error = h.get("last_error")
|
|
442
|
+
items += _as_int(h.get("items_window"))
|
|
443
|
+
return last_success, last_error_at, last_error, items
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def _read_signal(
|
|
447
|
+
client: redis.Redis, signal: str, *, enabled: bool, now: float
|
|
448
|
+
) -> tuple[dict[str, Any], set[str]]:
|
|
449
|
+
"""Read + aggregate one signal's live pods. Returns (block, live-pod set)."""
|
|
450
|
+
idx_key = f"{_KEY_PREFIX}idx:{signal}"
|
|
451
|
+
cutoff = now - _staleness_seconds()
|
|
452
|
+
# Trim members that have gone stale, then read only the live ones by score —
|
|
453
|
+
# bounded by live-pod count, never a KEYS/SCAN over the keyspace.
|
|
454
|
+
client.zremrangebyscore(idx_key, "-inf", f"({cutoff}")
|
|
455
|
+
# redis-py types read commands as Awaitable|Any for the sync/async-generic client;
|
|
456
|
+
# the sync client returns the concrete list (same pattern the throttles type-ignore).
|
|
457
|
+
pod_ids: list[str] = list(client.zrangebyscore(idx_key, cutoff, "+inf")) # type: ignore[arg-type]
|
|
458
|
+
hashes: list[dict[str, str]] = []
|
|
459
|
+
if pod_ids:
|
|
460
|
+
pipe = client.pipeline()
|
|
461
|
+
for pod in pod_ids:
|
|
462
|
+
pipe.hgetall(f"{_KEY_PREFIX}pod:{signal}:{pod}")
|
|
463
|
+
hashes = pipe.execute()
|
|
464
|
+
|
|
465
|
+
last_success, last_error_at, last_error, items = _fold_pod_hashes(hashes)
|
|
466
|
+
|
|
467
|
+
state = _state(
|
|
468
|
+
signal, enabled=enabled, last_success=last_success, last_error_at=last_error_at, now=now
|
|
469
|
+
)
|
|
470
|
+
block = {
|
|
471
|
+
"state": state,
|
|
472
|
+
"last_success_at": _iso(last_success),
|
|
473
|
+
"last_success_age_seconds": (int(now - last_success) if last_success is not None else None),
|
|
474
|
+
"items_per_window": items,
|
|
475
|
+
"last_error": last_error if state in ("failing", "stalled") else None,
|
|
476
|
+
"last_error_at": _iso(last_error_at) if state in ("failing", "stalled") else None,
|
|
477
|
+
"pods_reporting": len(pod_ids),
|
|
478
|
+
}
|
|
479
|
+
return block, set(pod_ids)
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
def _state(
|
|
483
|
+
signal: str,
|
|
484
|
+
*,
|
|
485
|
+
enabled: bool,
|
|
486
|
+
last_success: float | None,
|
|
487
|
+
last_error_at: float | None,
|
|
488
|
+
now: float,
|
|
489
|
+
) -> str:
|
|
490
|
+
"""Server-side export-health verdict for one signal (ADR-0601 §4).
|
|
491
|
+
|
|
492
|
+
Kept server-side (not derived in the browser) so the card, the API, and any
|
|
493
|
+
future MCP/agent consumer agree on "is export healthy?". ``stalled`` requires a
|
|
494
|
+
surviving ``last_success`` — an evicted or never-seen record can only reach the
|
|
495
|
+
neutral ``never``, never the red alarm.
|
|
496
|
+
"""
|
|
497
|
+
if not enabled:
|
|
498
|
+
return "disabled"
|
|
499
|
+
# Most recent recorded outcome is an error → failing (the red "queue backing up").
|
|
500
|
+
# `last_success is None` first so the comparison only runs when it is a float.
|
|
501
|
+
if last_error_at is not None and (last_success is None or last_error_at > last_success):
|
|
502
|
+
return "failing"
|
|
503
|
+
if last_success is None:
|
|
504
|
+
return "never" # enabled but nothing exported yet (or record expired/evicted)
|
|
505
|
+
if now - last_success <= _healthy_within_seconds():
|
|
506
|
+
return "healthy"
|
|
507
|
+
# A live record whose last success is beyond the healthy window. Metrics export
|
|
508
|
+
# on a fixed cadence, so an overdue success is authoritative evidence the
|
|
509
|
+
# collector is gone → stalled. Traces are volume-driven, so a quiet system
|
|
510
|
+
# legitimately produces none → neutral idle (never a red alarm).
|
|
511
|
+
return "stalled" if signal == SIGNAL_METRICS else "idle"
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def _as_float(value: str | None) -> float | None:
|
|
515
|
+
if value is None:
|
|
516
|
+
return None
|
|
517
|
+
try:
|
|
518
|
+
return float(value)
|
|
519
|
+
except (TypeError, ValueError):
|
|
520
|
+
return None
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def _as_int(value: str | None) -> int:
|
|
524
|
+
try:
|
|
525
|
+
return int(value) if value is not None else 0
|
|
526
|
+
except (TypeError, ValueError):
|
|
527
|
+
return 0
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def reset_pool_for_testing() -> None:
|
|
531
|
+
"""Drop the cached connection pool so a test can rebind ``REDIS_URL``."""
|
|
532
|
+
global _pool
|
|
533
|
+
_pool = None
|