From 366a105f7e00a1a25d3f5d3f0069365d7bab7322 Mon Sep 17 00:00:00 2001 From: Rodrigo Horie Date: Tue, 9 Jun 2026 10:18:29 -0300 Subject: [PATCH] feat: inject x-ai-description from overlay file during schema generation Many endpoints have human-readable AI descriptions that were added downstream in aap-mcp-server (PRs #73 and #119) but never backported as @extend_schema_if_available decorators. This causes 470 out of 631 x-ai-description entries to be lost every time the spec is regenerated. Add a JSON overlay file (openapi_ai_descriptions.json) containing the missing descriptions keyed by operationId, and a drf-spectacular postprocessing hook that merges them into the generated schema for any operation that doesn't already have x-ai-description from a decorator. Co-Authored-By: Claude Opus 4.6 (1M context) --- awx/api/openapi_ai_descriptions.json | 471 +++++++++++++++++++++++++ awx/api/schema.py | 33 ++ awx/main/tests/unit/api/test_schema.py | 129 ++++++- awx/settings/defaults.py | 7 +- 4 files changed, 637 insertions(+), 3 deletions(-) create mode 100644 awx/api/openapi_ai_descriptions.json diff --git a/awx/api/openapi_ai_descriptions.json b/awx/api/openapi_ai_descriptions.json new file mode 100644 index 0000000000..c970e61e76 --- /dev/null +++ b/awx/api/openapi_ai_descriptions.json @@ -0,0 +1,471 @@ +{ + "activity_stream_retrieve": "Retrieve an audit trail entry for tracking all changes within the system", + "ad_hoc_commands_activity_stream_list": "List activity stream of an ad hoc command", + "ad_hoc_commands_create": "Create an ad hoc command", + "ad_hoc_commands_destroy": "Delete an ad hoc command", + "ad_hoc_commands_events_list": "List events of an ad hoc command", + "ad_hoc_commands_list": "List ad hoc commands", + "ad_hoc_commands_notifications_list": "List notifications of an ad hoc command", + "ad_hoc_commands_retrieve": "Retrieve an ad hoc command", + "ad_hoc_commands_stdout_retrieve": "Retrieve a stdout output of an ad hoc command", + "analytics_adoption_rate_options_retrieve": "Retrieve single analytics adoption rate option", + "analytics_adoption_rate_retrieve": "Retrieve single analytics adoption rate", + "analytics_event_explorer_options_retrieve": "Retrieve single analytics event explorer option", + "analytics_event_explorer_retrieve": "Retrieve single analytics event explorer", + "analytics_host_explorer_options_retrieve": "Retrieve single analytics host explorer option", + "analytics_host_explorer_retrieve": "Retrieve single analytics host explorer", + "analytics_job_explorer_options_retrieve": "Retrieve single analytics job explorer option", + "analytics_job_explorer_retrieve": "Retrieve single analytics job explorer", + "analytics_probe_template_for_hosts_options_retrieve": "Retrieve single analytics probe template for hosts option", + "analytics_probe_template_for_hosts_retrieve": "Retrieve single analytics probe template for host", + "analytics_probe_templates_options_retrieve": "Retrieve single analytics probe templates option", + "analytics_probe_templates_retrieve": "Retrieve single analytics probe template", + "analytics_reports_retrieve": "Retrieve single analytics report", + "analytics_roi_templates_options_retrieve": "Retrieve single analytics roi templates option", + "analytics_roi_templates_retrieve": "Retrieve single analytics roi template", + "constructed_inventories_create": "Create a constructed inventory", + "constructed_inventories_destroy": "Delete a constructed inventory", + "constructed_inventories_partial_update": "Update a constructed inventory", + "constructed_inventories_retrieve": "Retrieve a constructed inventory", + "constructed_inventories_update": "Update a constructed inventory", + "credential_input_sources_create": "Create a credential input source", + "credential_input_sources_destroy": "Delete a credential input source", + "credential_input_sources_list": "List credential input sources", + "credential_input_sources_partial_update": "Update a credential input source", + "credential_input_sources_retrieve": "Retrieve a credential input source", + "credential_input_sources_update": "Update a credential input source", + "credential_types_credentials_create": "Create a credential of a credential type", + "credential_types_credentials_list": "List credentials of a credential type", + "credential_types_retrieve": "Retrieve a credential type", + "credential_types_test_retrieve": "Retrieve single test for a credential_type", + "credentials_destroy": "Delete a credential", + "credentials_input_sources_create": "Create new source for a credential", + "credentials_input_sources_list": "List all sources for a credential", + "credentials_object_roles_list": "List roles of a credential", + "credentials_owner_teams_list": "List all teams for a credential", + "credentials_owner_users_list": "List all users for a credential", + "credentials_partial_update": "Update a credential", + "credentials_retrieve": "Retrieve a credential", + "credentials_test_retrieve": "Retrieve a test external credential", + "credentials_update": "Update a credential", + "execution_environments_activity_stream_list": "List activity stream of an execution environment", + "execution_environments_copy_create": "Create new copy for an execution_environment", + "execution_environments_copy_retrieve": "Retrieve single copy for an execution_environment", + "execution_environments_retrieve": "Retrieve an execution environment", + "execution_environments_unified_job_templates_list": "List unified job templates using this execution environment", + "feature_flags_state_retrieve": "Retrieve single feature flags state", + "feature_flags_states_list": "List all feature flags states", + "feature_flags_states_retrieve": "Retrieve single feature flags state", + "groups_activity_stream_list": "List activity stream for a group", + "groups_ad_hoc_commands_create": "Create an ad hoc command for a group", + "groups_ad_hoc_commands_list": "List ad hoc commands for a group", + "groups_all_hosts_list": "List all hosts for a group", + "groups_children_create": "Create new child for a group", + "groups_children_list": "List all children for a group", + "groups_destroy": "Delete a group", + "groups_hosts_create": "Create a host of a group", + "groups_hosts_list": "List hosts of a group", + "groups_inventory_sources_list": "List inventory sources of a group", + "groups_job_events_list": "List job events for a group", + "groups_job_host_summaries_list": "List job host summaries for a group", + "groups_partial_update": "Update a group", + "groups_potential_children_list": "List all children for a group", + "groups_retrieve": "Retrieve a group", + "groups_update": "Update a group", + "groups_variable_data_partial_update": "Update a variable datum for a group", + "groups_variable_data_retrieve": "Retrieve a variable datum for a group", + "groups_variable_data_update": "Update a variable datum for a group", + "host_metric_summary_monthly_list": "List monthly summaries for host metrics", + "host_metrics_list": "List host metrics", + "host_metrics_retrieve": "Retrieve a host metric", + "hosts_activity_stream_list": "List activity stream for a host", + "hosts_ad_hoc_command_events_list": "List events of ad hoc command of a host", + "hosts_ad_hoc_commands_create": "Create an ad hoc command of a host", + "hosts_ad_hoc_commands_list": "List ad hoc commands of a host", + "hosts_all_groups_list": "List all groups for a host", + "hosts_create": "Create a host", + "hosts_groups_create": "Create the list of groups a host is directly a member of", + "hosts_groups_list": "List the list of groups a host is directly a member of", + "hosts_inventory_sources_list": "List inventory sources of a host", + "hosts_job_events_list": "List job events of a host", + "hosts_job_host_summaries_list": "List job summaries of a host", + "hosts_partial_update": "Update a host", + "hosts_retrieve": "Retrieve a host", + "hosts_smart_inventories_list": "List all inventories for a host", + "hosts_update": "Update a host", + "hosts_variable_data_partial_update": "Update a variable datum for a host", + "hosts_variable_data_update": "Update a variable datum for a host", + "instance_groups_destroy": "Delete an instance group", + "instance_groups_instances_create": "Create an instance of an instance group", + "instance_groups_instances_list": "List instance of an instance group", + "instance_groups_jobs_list": "List jobs of an instance group", + "instance_groups_object_roles_list": "List all roles for an instance_group", + "instance_groups_partial_update": "Update an instance group", + "instance_groups_retrieve": "Retrieve an instance group", + "instance_groups_update": "Update an instance group", + "instances_instance_groups_create": "Create an instance group of an instance", + "instances_instance_groups_list": "List instance groups of an instance", + "instances_jobs_list": "List jobs executed on an instance", + "instances_list": "List instances", + "instances_partial_update": "Update an instance", + "instances_peers_list": "List all peers for an instance", + "instances_retrieve": "Retrieve an instance", + "instances_update": "Update an instance", + "inventories_access_list_list": "List users who can access the inventory", + "inventories_ad_hoc_commands_create": "Create an ad hoc command for an inventory", + "inventories_ad_hoc_commands_list": "List ad hoc command for an inventory", + "inventories_copy_create": "Create a copy of an inventory", + "inventories_copy_retrieve": "Retrieve a copy of an inventory", + "inventories_create": "Create an inventory", + "inventories_destroy": "Delete an inventory", + "inventories_groups_create": "Create a group of an inventory", + "inventories_groups_list": "List groups of an inventory", + "inventories_hosts_create": "Create a host of an inventory", + "inventories_hosts_list": "List hosts of an inventory", + "inventories_instance_groups_create": "Create an instance group of an inventory", + "inventories_instance_groups_list": "List instance groups of an inventory", + "inventories_inventory_sources_create": "Create an inventory source", + "inventories_inventory_sources_list": "List inventory sources", + "inventories_job_templates_list": "List job templates using an inventory", + "inventories_labels_list": "List labels of an inventory", + "inventories_object_roles_list": "List roles of an inventory", + "inventories_partial_update": "Update an inventory", + "inventories_retrieve": "Retrieve an inventory", + "inventories_update": "Update an inventory", + "inventories_update_inventory_sources_retrieve": "Retrieve single source for an inventory", + "inventories_variable_data_partial_update": "Partially update existing datum for an inventory", + "inventories_variable_data_retrieve": "Retrieve single datum for an inventory", + "inventories_variable_data_update": "Update existing datum for an inventory", + "inventory_sources_activity_stream_list": "List activity stream of an inventory source", + "inventory_sources_create": "Create an inventory source", + "inventory_sources_credentials_create": "Create a credential of an inventory source", + "inventory_sources_credentials_list": "List credentials of an inventory source", + "inventory_sources_destroy": "Delete an inventory source", + "inventory_sources_groups_destroy": "Delete a group of an inventory source", + "inventory_sources_groups_list": "List groups of an inventory source", + "inventory_sources_hosts_destroy": "Delete a host of an inventory source", + "inventory_sources_hosts_list": "List hosts of an inventory source", + "inventory_sources_inventory_updates_list": "List inventory updates of an inventory source", + "inventory_sources_list": "List inventory sources", + "inventory_sources_notification_templates_error_list": "List notification templates triggered on inventory source update error", + "inventory_sources_notification_templates_started_list": "List notification templates triggered on inventory source update start", + "inventory_sources_notification_templates_success_list": "List notification templates triggered on inventory source update success", + "inventory_sources_partial_update": "Update an inventory source", + "inventory_sources_retrieve": "Retrieve an inventory source", + "inventory_sources_schedules_create": "Create a schedule of an inventory source", + "inventory_sources_schedules_list": "List schedules of an inventory source", + "inventory_sources_update": "Update an inventory source", + "inventory_sources_update_retrieve": "Retrieve an update for an inventory source", + "inventory_updates_cancel_create": "Create a cancel for an inventory update", + "inventory_updates_cancel_retrieve": "Retrieve a cancel for an inventory update", + "inventory_updates_credentials_list": "List credentials of an inventory update", + "inventory_updates_destroy": "Delete an inventory update", + "inventory_updates_events_list": "List events of an inventory update", + "inventory_updates_list": "List inventory updates", + "inventory_updates_notifications_list": "List notifications of an inventory update", + "inventory_updates_retrieve": "Retrieve an inventory update", + "inventory_updates_stdout_retrieve": "Retrieve a stdout output of an inventory update", + "job_events_children_list": "List child events of a job event", + "job_events_retrieve": "Retrieve a job event detail", + "job_host_summaries_retrieve": "Retrieve a job host summary detail", + "job_templates_access_list_list": "List users who can access a job template", + "job_templates_activity_stream_list": "List activity stream of a job template", + "job_templates_copy_create": "Create a copy a job template", + "job_templates_copy_retrieve": "Retrieve a copy a job template", + "job_templates_create": "Create a job template", + "job_templates_credentials_create": "Create a credential of a job template", + "job_templates_credentials_list": "List credentials of a job template", + "job_templates_destroy": "Delete a job template", + "job_templates_instance_groups_create": "Create an instance group of a job template", + "job_templates_instance_groups_list": "List instance groups of a job template", + "job_templates_jobs_list": "List jobs of a job template", + "job_templates_labels_list": "List labels of a job template", + "job_templates_launch_retrieve": "Retrieve single launch for a job_template", + "job_templates_notification_templates_error_create": "Create a notification templates triggered on job error", + "job_templates_notification_templates_error_list": "List notification templates triggered on job error", + "job_templates_notification_templates_started_create": "Create a notification templates triggered on job start", + "job_templates_notification_templates_started_list": "List notification templates triggered on job start", + "job_templates_notification_templates_success_create": "Create a notification templates triggered on job success", + "job_templates_notification_templates_success_list": "List notification templates triggered on job success", + "job_templates_object_roles_list": "List roles of a job template", + "job_templates_partial_update": "Update a job template", + "job_templates_retrieve": "Retrieve a job template", + "job_templates_schedules_create": "Create a schedule of a job template", + "job_templates_schedules_list": "List schedules of a job template", + "job_templates_slice_workflow_jobs_create": "Create new job for a job_template", + "job_templates_slice_workflow_jobs_list": "List all jobs for a job_template", + "job_templates_update": "Update a job template", + "jobs_activity_stream_list": "List activity stream of a job", + "jobs_cancel_retrieve": "Retrieve a cancel for a job", + "jobs_create_schedule_retrieve": "Retrieve single schedule for a job", + "jobs_credentials_list": "List credentials of a job", + "jobs_destroy": "Delete a job", + "jobs_job_events_list": "List job events of a job", + "jobs_job_host_summaries_list": "List job host summaries of a job", + "jobs_labels_list": "List labels of a job", + "jobs_notifications_list": "List notifications of a job", + "jobs_relaunch_retrieve": "Retrieve single relaunch for a job", + "jobs_retrieve": "Retrieve a job", + "labels_create": "Create a label", + "labels_list": "List labels", + "labels_partial_update": "Update a label", + "labels_retrieve": "Retrieve a label", + "labels_update": "Update a label", + "me_list": "List current authenticated user", + "notification_templates_copy_create": "Create a copy a notification template", + "notification_templates_copy_retrieve": "Retrieve a copy a notification template", + "notification_templates_notifications_list": "List notifications of a notification template", + "notification_templates_retrieve": "Retrieve a notification template", + "notifications_list": "List notifications", + "notifications_retrieve": "Retrieve a notification", + "organizations_access_list_list": "List users who can access the organization", + "organizations_activity_stream_list": "List activity stream for an organization", + "organizations_admins_create": "Create new admin for an organization", + "organizations_admins_list": "List all admins for an organization", + "organizations_create": "Create an organization", + "organizations_credentials_create": "Create a credential of an organization", + "organizations_credentials_list": "List credentials of an organization", + "organizations_destroy": "Delete an organization", + "organizations_execution_environments_create": "Create an execution environment of an organization", + "organizations_execution_environments_list": "List execution environments of an organization", + "organizations_galaxy_credentials_create": "Create new credential for an organization", + "organizations_galaxy_credentials_list": "List all credentials for an organization", + "organizations_instance_groups_create": "Create an instance group of an organization", + "organizations_instance_groups_list": "List instance groups of an organization", + "organizations_inventories_list": "List inventories of an organization", + "organizations_job_templates_create": "Create a job template of an organization", + "organizations_job_templates_list": "List job templates of an organization", + "organizations_notification_templates_approvals_create": "Create new approval for an organization", + "organizations_notification_templates_approvals_list": "List all approvals for an organization", + "organizations_notification_templates_create": "Create a notification template of an organization", + "organizations_notification_templates_error_create": "Create new error for an organization", + "organizations_notification_templates_error_list": "List all error for an organization", + "organizations_notification_templates_list": "List notification templates of an organization", + "organizations_notification_templates_started_create": "Create new started for an organization", + "organizations_notification_templates_started_list": "List all started for an organization", + "organizations_notification_templates_success_create": "Create new success for an organization", + "organizations_notification_templates_success_list": "List all success for an organization", + "organizations_object_roles_list": "List roles of an organization", + "organizations_partial_update": "Update an organization", + "organizations_projects_create": "Create a project of an organization", + "organizations_projects_list": "List projects of an organization", + "organizations_retrieve": "Retrieve an organization", + "organizations_retrieve_2": "Retrieve an organization", + "organizations_teams_create": "Create a team of an organization", + "organizations_teams_list": "List teams of an organization", + "organizations_update": "Update an organization", + "organizations_users_create": "Create a user of an organization", + "organizations_users_list": "List users of an organization", + "organizations_workflow_job_templates_create": "Create a workflow job template of an organization", + "organizations_workflow_job_templates_list": "List workflow job templates of an organization", + "project_updates_cancel_create": "Create new cancel for a project_update", + "project_updates_cancel_retrieve": "Retrieve single cancel for a project_update", + "project_updates_destroy": "Delete a project update", + "project_updates_events_list": "List all events for a project_update", + "project_updates_list": "List project updates", + "project_updates_notifications_list": "List notifications of a project update", + "project_updates_retrieve": "Retrieve a project update", + "project_updates_scm_inventory_updates_list": "List all updates for a project_update", + "project_updates_stdout_retrieve": "Retrieve single stdout for a project_update", + "projects_access_list_list": "List users who can access the project", + "projects_activity_stream_list": "List activity stream for a project", + "projects_copy_create": "Create a copy of a project", + "projects_copy_retrieve": "Retrieve a copy of a project", + "projects_create": "Create a project", + "projects_destroy": "Delete a project", + "projects_inventories_retrieve": "Retrieve an inventory from a project", + "projects_notification_templates_error_create": "Create a notification template for project error events", + "projects_notification_templates_error_list": "List notification templates for project error events", + "projects_notification_templates_started_create": "Create a notification template for project started events", + "projects_notification_templates_started_list": "List notification templates for project started events", + "projects_notification_templates_success_create": "Create a notification template for project success events", + "projects_notification_templates_success_list": "List notification templates for project success events", + "projects_object_roles_list": "List roles of a project", + "projects_partial_update": "Update a project", + "projects_playbooks_retrieve": "Retrieve single playbook for a project", + "projects_project_updates_list": "List project updates of a project", + "projects_retrieve": "Retrieve a project", + "projects_schedules_create": "Create a schedule of a project", + "projects_schedules_list": "List schedules of a project", + "projects_scm_inventory_sources_list": "List all sources for a project", + "projects_teams_list": "List teams with access to a project", + "projects_update": "Update a project", + "projects_update_retrieve": "Retrieve single update for a project", + "receptor_addresses_list": "List receptor addresses", + "receptor_addresses_retrieve": "Retrieve a receptor address", + "role_definitions_create": "Create a RBAC roles defining permissions that can be managed and assigned to users and teams", + "role_definitions_destroy": "Delete a RBAC roles defining permissions that can be managed and assigned to users and teams", + "role_definitions_list": "List RBAC roles defining permissions that can be managed and assigned to users and teams", + "role_definitions_partial_update": "Update a RBAC roles defining permissions that can be managed and assigned to users and teams", + "role_definitions_retrieve": "Retrieve a RBAC roles defining permissions that can be managed and assigned to users and teams", + "role_definitions_team_assignments_list": "List all assignments for a role_definition", + "role_definitions_update": "Update a RBAC roles defining permissions that can be managed and assigned to users and teams", + "role_definitions_user_assignments_list": "List all assignments for a role_definition", + "role_metadata_retrieve": "Retrieve single role metadatum", + "role_team_access_list": "List all role team access", + "role_team_access_list_2": "List all role team access", + "role_team_assignments_create": "Create a RBAC role grants assigning permissions to team for specific resources", + "role_team_assignments_destroy": "Delete a RBAC role grants assigning permissions to team for specific resources", + "role_team_assignments_list": "List RBAC role grants assigning permissions to teams for specific resources", + "role_team_assignments_retrieve": "Retrieve a RBAC role grants assigning permissions to team for specific resources", + "role_user_access_list": "List all role user access", + "role_user_access_list_2": "List all role user access", + "role_user_assignments_create": "Create a RBAC role grants assigning permissions to user for specific resources", + "role_user_assignments_destroy": "Delete a RBAC role grants assigning permissions to user for specific resources", + "role_user_assignments_list": "List RBAC role grants assigning permissions to users for specific resources", + "role_user_assignments_retrieve": "Retrieve a RBAC role grants assigning permissions to user for specific resources", + "roles_list": "List roles", + "roles_retrieve": "Retrieve a role", + "roles_teams_list": "List teams with a role", + "roles_users_list": "List users with a role", + "schedules_create": "Create a schedule", + "schedules_credentials_create": "Create a credential of a schedule", + "schedules_credentials_list": "List credentials of a schedule", + "schedules_destroy": "Delete a schedule", + "schedules_instance_groups_create": "Create an instance group of a schedule", + "schedules_instance_groups_list": "List instance groups of a schedule", + "schedules_jobs_list": "List jobs created by a schedule", + "schedules_labels_list": "List labels of a schedule", + "schedules_list": "List schedules", + "schedules_partial_update": "Update a schedule", + "schedules_retrieve": "Retrieve a schedule", + "schedules_update": "Update a schedule", + "service_index_metadata_retrieve": "Retrieve single service index metadatum", + "service_index_resource_types_list": "List all service index resource types", + "service_index_resource_types_manifest_retrieve": "Retrieve single manifest for a resource-type", + "service_index_resource_types_retrieve": "Retrieve single service index resource type", + "service_index_resources_create": "Create new service index resource", + "service_index_resources_destroy": "Delete existing service index resource", + "service_index_resources_list": "List all service index resources", + "service_index_resources_partial_update": "Partially update existing service index resource", + "service_index_resources_retrieve": "Retrieve single service index resource", + "service_index_resources_update": "Update existing service index resource", + "service_index_retrieve": "Retrieve single service index", + "service_index_role_permissions_list": "List all service index role permissions", + "service_index_role_team_assignments_assign_create": "Create new service index role team assignments assign", + "service_index_role_team_assignments_list": "List all service index role team assignments", + "service_index_role_team_assignments_unassign_create": "Create new service index role team assignments unassign", + "service_index_role_types_list": "List all service index role types", + "service_index_role_user_assignments_assign_create": "Create new service index role user assignments assign", + "service_index_role_user_assignments_list": "List all service index role user assignments", + "service_index_role_user_assignments_unassign_create": "Create new service index role user assignments unassign", + "settings_destroy": "Delete existing setting", + "settings_logging_test_create": "Create new settings logging test", + "settings_retrieve": "Retrieve single setting", + "settings_update": "Update existing setting", + "system_job_templates_jobs_list": "List system jobs of a system job template", + "system_job_templates_notification_templates_error_create": "Create a notification templates triggered on system job error", + "system_job_templates_notification_templates_error_list": "List notification templates triggered on system job error", + "system_job_templates_notification_templates_started_create": "Create a notification templates triggered on system job start", + "system_job_templates_notification_templates_started_list": "List notification templates triggered on system job start", + "system_job_templates_notification_templates_success_create": "Create a notification templates triggered on system job success", + "system_job_templates_notification_templates_success_list": "List notification templates triggered on system job success", + "system_job_templates_retrieve": "Retrieve a system job template", + "system_job_templates_schedules_create": "Create a schedule of a system job template", + "system_job_templates_schedules_list": "List schedules of a system job template", + "system_jobs_cancel_create": "Create a cancel for a system job", + "system_jobs_cancel_retrieve": "Retrieve a cancel for a system job", + "system_jobs_destroy": "Delete a system job", + "system_jobs_events_list": "List events of a system job", + "system_jobs_notifications_list": "List notifications of a system job", + "system_jobs_retrieve": "Retrieve a system job", + "teams_access_list_list": "List users who can access the team", + "teams_activity_stream_list": "List activity stream for a team", + "teams_create": "Create a team", + "teams_credentials_create": "Create a credentials owned by a team", + "teams_credentials_list": "List credentials owned by a team", + "teams_destroy": "Delete a team", + "teams_list": "List teams", + "teams_object_roles_list": "List object roles of a team", + "teams_partial_update": "Update a team", + "teams_projects_list": "List projects accessible to a team", + "teams_retrieve": "Retrieve a team", + "teams_roles_list": "List roles of a team", + "teams_update": "Update a team", + "teams_users_create": "Create a user of a team", + "teams_users_list": "List users of a team", + "unified_job_templates_list": "List unified job templates", + "unified_jobs_list": "List unified jobs", + "users_access_list_list": "List users who can access the user", + "users_activity_stream_list": "List activity stream for a user", + "users_admin_of_organizations_retrieve": "Retrieve single organization for an user", + "users_create": "Create a user", + "users_credentials_create": "Create a credentials owned by a user", + "users_credentials_list": "List credentials owned by a user", + "users_destroy": "Delete a user", + "users_list": "List users", + "users_organizations_retrieve": "Retrieve an organization of a user", + "users_partial_update": "Update a user", + "users_projects_list": "List projects accessible to a user", + "users_retrieve": "Retrieve a user", + "users_roles_list": "List roles of a user", + "users_teams_list": "List teams of a user", + "users_update": "Update a user", + "workflow_approval_templates_approvals_list": "List all approvals for a workflow_approval_template", + "workflow_approval_templates_destroy": "Delete a workflow approval template detail", + "workflow_approval_templates_partial_update": "Update a workflow approval template detail", + "workflow_approval_templates_retrieve": "Retrieve a workflow approval template detail", + "workflow_approval_templates_update": "Update a workflow approval template detail", + "workflow_approvals_approve_retrieve": "Retrieve single approve for a workflow_approval", + "workflow_approvals_deny_retrieve": "Retrieve single deny for a workflow_approval", + "workflow_approvals_destroy": "Delete a workflow approval", + "workflow_approvals_retrieve": "Retrieve a workflow approval", + "workflow_job_nodes_always_nodes_list": "List always nodes of a workflow job node", + "workflow_job_nodes_credentials_list": "List credentials of a workflow job node", + "workflow_job_nodes_failure_nodes_list": "List failure nodes of a workflow job node", + "workflow_job_nodes_instance_groups_create": "Create an instance group of a workflow job node", + "workflow_job_nodes_instance_groups_list": "List instance groups of a workflow job node", + "workflow_job_nodes_labels_list": "List labels of a workflow job node", + "workflow_job_nodes_list": "List workflow job nodes", + "workflow_job_nodes_retrieve": "Retrieve a workflow job node", + "workflow_job_nodes_success_nodes_list": "List success nodes of a workflow job node", + "workflow_job_template_nodes_always_nodes_create": "Create new node for a workflow_job_template_node", + "workflow_job_template_nodes_always_nodes_list": "List all nodes for a workflow_job_template_node", + "workflow_job_template_nodes_create": "Create a workflow job template node", + "workflow_job_template_nodes_create_approval_template_retrieve": "Retrieve single template for a workflow_job_template_node", + "workflow_job_template_nodes_credentials_create": "Create a credential of a workflow job template node", + "workflow_job_template_nodes_credentials_list": "List credentials of a workflow job template node", + "workflow_job_template_nodes_destroy": "Delete a workflow job template node", + "workflow_job_template_nodes_failure_nodes_create": "Create new node for a workflow_job_template_node", + "workflow_job_template_nodes_failure_nodes_list": "List all nodes for a workflow_job_template_node", + "workflow_job_template_nodes_instance_groups_create": "Create an instance group of a workflow job template node", + "workflow_job_template_nodes_instance_groups_list": "List instance groups of a workflow job template node", + "workflow_job_template_nodes_labels_list": "List labels of a workflow job template node", + "workflow_job_template_nodes_list": "List workflow job template nodes", + "workflow_job_template_nodes_partial_update": "Update a workflow job template node", + "workflow_job_template_nodes_retrieve": "Retrieve a workflow job template node", + "workflow_job_template_nodes_success_nodes_create": "Create new node for a workflow_job_template_node", + "workflow_job_template_nodes_success_nodes_list": "List all nodes for a workflow_job_template_node", + "workflow_job_template_nodes_update": "Update a workflow job template node", + "workflow_job_templates_access_list_list": "List users who can access a workflow job template", + "workflow_job_templates_activity_stream_list": "List activity stream of a workflow job template", + "workflow_job_templates_copy_create": "Create a copy a workflow job template", + "workflow_job_templates_create": "Create a workflow job template", + "workflow_job_templates_destroy": "Delete a workflow job template", + "workflow_job_templates_labels_list": "List labels of a workflow job template", + "workflow_job_templates_launch_retrieve": "Retrieve a launch a workflow job from a workflow job template", + "workflow_job_templates_notification_templates_approvals_create": "Create a notification templates triggered on workflow approval", + "workflow_job_templates_notification_templates_approvals_list": "List notification templates triggered on workflow approval", + "workflow_job_templates_notification_templates_error_create": "Create a notification templates triggered on workflow job error", + "workflow_job_templates_notification_templates_error_list": "List notification templates triggered on workflow job error", + "workflow_job_templates_notification_templates_started_create": "Create a notification templates triggered on workflow job start", + "workflow_job_templates_notification_templates_started_list": "List notification templates triggered on workflow job start", + "workflow_job_templates_notification_templates_success_create": "Create a notification templates triggered on workflow job success", + "workflow_job_templates_notification_templates_success_list": "List notification templates triggered on workflow job success", + "workflow_job_templates_object_roles_list": "List roles of a workflow job template", + "workflow_job_templates_partial_update": "Update a workflow job template", + "workflow_job_templates_retrieve": "Retrieve a workflow job template", + "workflow_job_templates_schedules_create": "Create a schedule of a workflow job template", + "workflow_job_templates_schedules_list": "List schedules of a workflow job template", + "workflow_job_templates_update": "Update a workflow job template", + "workflow_job_templates_workflow_jobs_list": "List workflow jobs of a workflow job template", + "workflow_job_templates_workflow_nodes_create": "Create new node for a workflow_job_template", + "workflow_job_templates_workflow_nodes_list": "List all nodes for a workflow_job_template", + "workflow_jobs_activity_stream_list": "List activity stream of a workflow job", + "workflow_jobs_cancel_retrieve": "Retrieve a cancel for a workflow job", + "workflow_jobs_destroy": "Delete a workflow job", + "workflow_jobs_labels_list": "List labels of a workflow job", + "workflow_jobs_notifications_list": "List notifications of a workflow job", + "workflow_jobs_retrieve": "Retrieve a workflow job", + "workflow_jobs_workflow_nodes_list": "List workflow nodes of a workflow job" +} diff --git a/awx/api/schema.py b/awx/api/schema.py index d2bf39ff79..0383111443 100644 --- a/awx/api/schema.py +++ b/awx/api/schema.py @@ -1,3 +1,5 @@ +import json +import os import warnings from rest_framework.permissions import IsAuthenticated @@ -53,6 +55,37 @@ def filter_credential_type_schema( return result +def inject_ai_descriptions( + result, + generator, # NOSONAR + request, # NOSONAR + public, # NOSONAR +): + """ + Inject x-ai-description into operations from the overlay file. + + Many endpoints have human-readable AI descriptions that were added + downstream but not backported as @extend_schema_if_available decorators. + This hook merges them from a JSON file keyed by operationId. + """ + overlay_path = os.path.join(os.path.dirname(__file__), 'openapi_ai_descriptions.json') + try: + with open(overlay_path) as f: + descriptions = json.load(f) + except (FileNotFoundError, json.JSONDecodeError): + return result + + for path_item in result.get('paths', {}).values(): + for operation in path_item.values(): + if not isinstance(operation, dict): + continue + op_id = operation.get('operationId') + if op_id and op_id in descriptions and 'x-ai-description' not in operation: + operation['x-ai-description'] = descriptions[op_id] + + return result + + class CustomAutoSchema(AutoSchema): """Custom AutoSchema to add swagger_topic to tags and handle deprecated endpoints.""" diff --git a/awx/main/tests/unit/api/test_schema.py b/awx/main/tests/unit/api/test_schema.py index 83ade9e16c..74c2ad2e98 100644 --- a/awx/main/tests/unit/api/test_schema.py +++ b/awx/main/tests/unit/api/test_schema.py @@ -1,6 +1,7 @@ import copy +import json import warnings -from unittest.mock import Mock, patch +from unittest.mock import Mock, mock_open, patch from rest_framework.permissions import IsAuthenticated @@ -10,6 +11,7 @@ from awx.api.schema import ( AuthenticatedSpectacularSwaggerView, AuthenticatedSpectacularRedocView, filter_credential_type_schema, + inject_ai_descriptions, ) @@ -422,3 +424,128 @@ class TestFilterCredentialTypeSchema: # PATCH schema: includes None (optional field) assert result['components']['schemas']['PatchedCredentialTypeRequest']['properties']['kind']['enum'] == ['cloud', 'net', None] + + +class TestInjectAiDescriptions: + """Unit tests for inject_ai_descriptions postprocessing hook.""" + + def _make_result(self, operations): + """Build a minimal OpenAPI result dict from a list of (path, method, operationId, existing_desc) tuples.""" + paths = {} + for path, method, op_id, desc in operations: + paths.setdefault(path, {})[method] = {'operationId': op_id} + if desc: + paths[path][method]['x-ai-description'] = desc + return {'paths': paths} + + def test_injects_missing_descriptions(self): + """Test that descriptions are injected for operations without x-ai-description.""" + overlay = {'op_list': 'List items', 'op_create': 'Create an item'} + result = self._make_result( + [ + ('/api/v2/items/', 'get', 'op_list', None), + ('/api/v2/items/', 'post', 'op_create', None), + ] + ) + + with patch('builtins.open', mock_open(read_data=json.dumps(overlay))): + returned = inject_ai_descriptions(result, None, None, None) + + assert result['paths']['/api/v2/items/']['get']['x-ai-description'] == 'List items' + assert result['paths']['/api/v2/items/']['post']['x-ai-description'] == 'Create an item' + assert returned is result + + def test_does_not_overwrite_existing_descriptions(self): + """Test that existing x-ai-description from decorators is preserved.""" + overlay = {'op_list': 'Overlay description'} + result = self._make_result( + [ + ('/api/v2/items/', 'get', 'op_list', 'Decorator description'), + ] + ) + + with patch('builtins.open', mock_open(read_data=json.dumps(overlay))): + inject_ai_descriptions(result, None, None, None) + + assert result['paths']['/api/v2/items/']['get']['x-ai-description'] == 'Decorator description' + + def test_skips_operations_not_in_overlay(self): + """Test that operations without a matching operationId in the overlay are unchanged.""" + overlay = {'op_other': 'Other description'} + result = self._make_result( + [ + ('/api/v2/items/', 'get', 'op_list', None), + ] + ) + + with patch('builtins.open', mock_open(read_data=json.dumps(overlay))): + inject_ai_descriptions(result, None, None, None) + + assert 'x-ai-description' not in result['paths']['/api/v2/items/']['get'] + + def test_handles_missing_overlay_file(self): + """Test graceful handling when the overlay file doesn't exist.""" + result = self._make_result( + [ + ('/api/v2/items/', 'get', 'op_list', None), + ] + ) + original = copy.deepcopy(result) + + with patch('builtins.open', side_effect=FileNotFoundError): + returned = inject_ai_descriptions(result, None, None, None) + + assert result == original + assert returned is result + + def test_handles_invalid_json(self): + """Test graceful handling when the overlay file contains invalid JSON.""" + result = self._make_result( + [ + ('/api/v2/items/', 'get', 'op_list', None), + ] + ) + original = copy.deepcopy(result) + + with patch('builtins.open', mock_open(read_data='not valid json')): + returned = inject_ai_descriptions(result, None, None, None) + + assert result == original + assert returned is result + + def test_handles_empty_result(self): + """Test graceful handling when result has no paths.""" + result = {} + overlay = {'op_list': 'List items'} + + with patch('builtins.open', mock_open(read_data=json.dumps(overlay))): + returned = inject_ai_descriptions(result, None, None, None) + + assert returned is result + + def test_skips_non_dict_path_items(self): + """Test that non-dict values in path items (e.g. parameters list) are skipped.""" + overlay = {'op_list': 'List items'} + result = { + 'paths': { + '/api/v2/items/': { + 'parameters': [{'name': 'id', 'in': 'path'}], + 'get': {'operationId': 'op_list'}, + } + } + } + + with patch('builtins.open', mock_open(read_data=json.dumps(overlay))): + inject_ai_descriptions(result, None, None, None) + + assert result['paths']['/api/v2/items/']['get']['x-ai-description'] == 'List items' + + def test_handles_operation_without_operation_id(self): + """Test that operations without operationId are skipped.""" + overlay = {'op_list': 'List items'} + result = {'paths': {'/api/v2/items/': {'get': {'summary': 'List'}}}} + + with patch('builtins.open', mock_open(read_data=json.dumps(overlay))): + inject_ai_descriptions(result, None, None, None) + + assert 'x-ai-description' not in result['paths']['/api/v2/items/']['get'] diff --git a/awx/settings/defaults.py b/awx/settings/defaults.py index c7e2db9f08..b3e3fe10dc 100644 --- a/awx/settings/defaults.py +++ b/awx/settings/defaults.py @@ -1038,8 +1038,11 @@ SPECTACULAR_SETTINGS = { # Use our custom schema class that handles swagger_topic and deprecated views 'DEFAULT_SCHEMA_CLASS': 'awx.api.schema.CustomAutoSchema', 'COMPONENT_SPLIT_REQUEST': True, - # Postprocessing hook to filter CredentialType enum values - 'POSTPROCESSING_HOOKS': ['awx.api.schema.filter_credential_type_schema'], + # Postprocessing hooks for OpenAPI schema generation + 'POSTPROCESSING_HOOKS': [ + 'awx.api.schema.filter_credential_type_schema', + 'awx.api.schema.inject_ai_descriptions', + ], 'SWAGGER_UI_SETTINGS': { 'deepLinking': True, 'persistAuthorization': True,