This is an automated email from the ASF dual-hosted git repository.

vincbeck pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 3e93c772281 Generate fab permission table (#72230)
3e93c772281 is described below

commit 3e93c7722813d6fec2f3212df3ed15fdd6416561
Author: Baha Bouali <[email protected]>
AuthorDate: Wed Sep 2 15:01:03 2026 +0100

    Generate fab permission table (#72230)
    
    * Generate the FAB REST API permission table from source
    
    * Also cover FAB's own user and role endpoints
    
    * Generate the table in place to avoid the glob toctree
    
    * Generate the table in place to avoid the glob toctree
---
 .pre-commit-config.yaml                            |  15 +
 providers/fab/docs/auth-manager/access-control.rst | 656 ++++++++++++++++++---
 scripts/ci/prek/fab_permissions_doc.py             | 416 +++++++++++++
 3 files changed, 1021 insertions(+), 66 deletions(-)

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index d23125b3851..e71e7fedd09 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -729,6 +729,21 @@ repos:
           ^scripts/ci/prek/extract_permissions\.py$|
           ^airflow-core/docs/security/api_permissions_ref\.rst$
         pass_filenames: false
+      - id: generate-fab-permissions-doc
+        name: Generate FAB auth manager REST API permission table
+        entry: ./scripts/ci/prek/fab_permissions_doc.py
+        language: python
+        files: >
+          (?x)
+          ^airflow-core/src/airflow/api_fastapi/core_api/routes/public/.*\.py$|
+          ^airflow-core/src/airflow/api_fastapi/core_api/security\.py$|
+          
^providers/fab/src/airflow/providers/fab/auth_manager/fab_auth_manager\.py$|
+          
^providers/fab/src/airflow/providers/fab/auth_manager/security_manager/override\.py$|
+          
^providers/fab/src/airflow/providers/fab/www/security/permissions\.py$|
+          ^scripts/ci/prek/extract_permissions\.py$|
+          ^scripts/ci/prek/fab_permissions_doc\.py$|
+          ^providers/fab/docs/auth-manager/access-control\.rst$
+        pass_filenames: false
       - id: update-tested-versions
         name: Update tested Python/DB/Kubernetes versions in docs
         entry: ./scripts/ci/prek/update_tested_versions.py
diff --git a/providers/fab/docs/auth-manager/access-control.rst 
b/providers/fab/docs/auth-manager/access-control.rst
index 52658bd6e80..761becef418 100644
--- a/providers/fab/docs/auth-manager/access-control.rst
+++ b/providers/fab/docs/auth-manager/access-control.rst
@@ -145,73 +145,597 @@ For individual Dags, the resource name is ``Dag:`` + the 
Dag ID.
 
 For example, if a user is trying to view Dag information for the 
``example_dag_id``, and the endpoint requires ``DAGs.can_read`` access, access 
will be granted if the user has either ``DAGs.can_read`` or 
``DAG:example_dag_id.can_read`` access.
 
-==================================================================================
 ====== ================================================================= 
============
 Stable API Permissions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-Endpoint                                                                       
    Method Permissions                                                       
Minimum Role
-==================================================================================
 ====== ================================================================= 
============
-/config                                                                        
    GET    Configurations.can_read                                           Op
-/connections                                                                   
    GET    Connections.can_read                                              Op
-/connections                                                                   
    POST   Connections.can_create                                            Op
-/connections/{connection_id}                                                   
    DELETE Connections.can_delete                                            Op
-/connections/{connection_id}                                                   
    PATCH  Connections.can_edit                                              Op
-/connections/{connection_id}                                                   
    GET    Connections.can_read                                              Op
-/dagSources/{file_token}                                                       
    GET    Dag Code.can_read                                                 
Viewer
-/dags                                                                          
    GET    Dags.can_read                                                     
Viewer
-/dags/{dag_id}                                                                 
    GET    Dags.can_read                                                     
Viewer
-/dags/{dag_id}                                                                 
    PATCH  Dags.can_edit                                                     
User
-/dags/{dag_id}/clearTaskInstances                                              
    PUT    Dags.can_edit, Dag Runs.can_edit, Task Instances.can_edit         
User
-/dags/{dag_id}/details                                                         
    GET    Dags.can_read                                                     
Viewer
-/dags/{dag_id}/tasks                                                           
    GET    Dags.can_read, Task Instances.can_read                            
Viewer
-/dags/{dag_id}/tasks/{task_id}                                                 
    GET    Dags.can_read, Task Instances.can_read                            
Viewer
-/dags/{dag_id}/dagRuns                                                         
    GET    Dags.can_read, Dag Runs.can_read                                  
Viewer
-/dags/{dag_id}/dagRuns                                                         
    POST   Dags.can_edit, Dag Runs.can_create                                
User
-/dags/{dag_id}/dagRuns/{dag_run_id}                                            
    DELETE Dags.can_edit, Dag Runs.can_delete                                
User
-/dags/{dag_id}/dagRuns/{dag_run_id}                                            
    GET    Dags.can_read, Dag Runs.can_read                                  
Viewer
-/dags/~/dagRuns/list                                                           
    POST   Dags.can_edit, Dag Runs.can_read                                  
User
-/assets                                                                        
    GET    Assets.can_read                                                   
Viewer
-/assets/{uri}                                                                  
    GET    Assets.can_read                                                   
Viewer
-/assets/events                                                                 
    GET    Assets.can_read                                                   
Viewer
-/eventLogs                                                                     
    GET    Audit Logs.can_read                                               
Admin
-                                                                               
           All Audit Logs.can_read (for rows not tied to a Dag)
-/eventLogs/{event_log_id}                                                      
    GET    Audit Logs.can_read                                               
Admin
-                                                                               
           All Audit Logs.can_read (for rows not tied to a Dag)
-/importErrors                                                                  
    GET    ImportError.can_read                                              
Viewer
-/importErrors/{import_error_id}                                                
    GET    ImportError.can_read                                              
Viewer
-/health                                                                        
    GET    None                                                              
Public
-/version                                                                       
    GET    None                                                              
Public
-/pools                                                                         
    GET    Pools.can_read                                                     Op
-/pools                                                                         
    POST   Pools.can_create                                                   Op
-/pools/{pool_name}                                                             
    DELETE Pools.can_delete                                                   Op
-/pools/{pool_name}                                                             
    GET    Pools.can_read                                                     Op
-/pools/{pool_name}                                                             
    PATCH  Pools.can_edit                                                     Op
-/providers                                                                     
    GET    Providers.can_read                                                 Op
-/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances                              
    GET    Dags.can_read, Dag Runs.can_read, Task Instances.can_read         
Viewer
-/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}                    
    GET    Dags.can_read, Dag Runs.can_read, Task Instances.can_read         
Viewer
-/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/links              
    GET    Dags.can_read, Dag Runs.can_read, Task Instances.can_read         
Viewer
-/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/logs/{task_try_number}
 GET    Dags.can_read, Dag Runs.can_read, Task Instances.can_read         Viewer
-/dags/~/dagRuns/~/taskInstances/list                                           
    POST   Dags.can_edit, Dag Runs.can_read, Task Instances.can_read         
User
-/variables                                                                     
    GET    Variables.can_read                                                Op
-/variables                                                                     
    POST   Variables.can_create                                              Op
-/variables/{variable_key}                                                      
    DELETE Variables.can_delete                                              Op
-/variables/{variable_key}                                                      
    GET    Variables.can_read                                                Op
-/variables/{variable_key}                                                      
    PATCH  Variables.can_edit                                                Op
-/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries        
    GET    Dags.can_read, Dag Runs.can_read,                                 
Viewer
-                                                                               
           Task Instances.can_read, XComs.can_read
-/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries/{xcom_key}
 GET    Dags.can_read, Dag Runs.can_read,                                 Viewer
-                                                                               
           Task Instances.can_read, XComs.can_read
-/users                                                                         
    GET    Users.can_read                                                    
Admin
-/users                                                                         
    POST   Users.can_create                                                  
Admin
-/users/{username}                                                              
    GET    Users.can_read                                                    
Admin
-/users/{username}                                                              
    PATCH  Users.can_edit                                                    
Admin
-/users/{username}                                                              
    DELETE Users.can_delete                                                  
Admin
-/roles                                                                         
    GET    Roles.can_read                                                    
Admin
-/roles                                                                         
    POST   Roles.can_create                                                  
Admin
-/roles/{role_name}                                                             
    GET    Roles.can_read                                                    
Admin
-/roles/{role_name}                                                             
    PATCH  Roles.can_edit                                                    
Admin
-/roles/{role_name}                                                             
    DELETE Roles.can_delete                                                  
Admin
-/permissions                                                                   
    GET    Permission Views.can_read                                         
Admin
-==================================================================================
 ====== ================================================================= 
============
+----------------------
+
+.. BEGIN GENERATED PERMISSIONS TABLE
+
+.. THE TABLE BELOW IS AUTO-GENERATED. DO NOT EDIT IT MANUALLY.
+   Regenerate with:  python scripts/ci/prek/fab_permissions_doc.py
+   Trigger:          prek run generate-fab-permissions-doc --all-files
+
+.. list-table:: Stable REST API permissions (FAB auth manager)
+   :header-rows: 1
+   :widths: 45 8 32 15
+
+   * - Endpoint
+     - Method
+     - Permissions
+     - Minimum role
+   * - ``/api/v2/assets``
+     - GET
+     - Asset Aliases.can_read
+     - Viewer
+   * - ``/api/v2/assets``
+     - GET
+     - Assets.can_read
+     - Viewer
+   * - ``/api/v2/assets/aliases``
+     - GET
+     - Asset Aliases.can_read
+     - Viewer
+   * - ``/api/v2/assets/aliases/{asset_alias_id}``
+     - GET
+     - Asset Aliases.can_read
+     - Viewer
+   * - ``/api/v2/assets/events``
+     - GET
+     - Assets.can_read
+     - Viewer
+   * - ``/api/v2/assets/events``
+     - POST
+     - Assets.can_create
+     - User
+   * - ``/api/v2/assets/{asset_id}``
+     - GET
+     - Asset Aliases.can_read
+     - Viewer
+   * - ``/api/v2/assets/{asset_id}``
+     - GET
+     - Assets.can_read
+     - Viewer
+   * - ``/api/v2/assets/{asset_id}/materialize``
+     - POST
+     - Assets.can_create
+     - User
+   * - ``/api/v2/assets/{asset_id}/queuedEvents``
+     - DELETE
+     - Assets.can_delete
+     - Op
+   * - ``/api/v2/assets/{asset_id}/queuedEvents``
+     - DELETE
+     - DAGs.can_edit
+     - User
+   * - ``/api/v2/assets/{asset_id}/queuedEvents``
+     - GET
+     - Assets.can_read
+     - Viewer
+   * - ``/api/v2/assets/{asset_id}/state-store``
+     - DELETE
+     - Assets.can_delete
+     - Op
+   * - ``/api/v2/assets/{asset_id}/state-store``
+     - GET
+     - Assets.can_read
+     - Viewer
+   * - ``/api/v2/assets/{asset_id}/state-store/{key:path}``
+     - DELETE
+     - Assets.can_delete
+     - Op
+   * - ``/api/v2/assets/{asset_id}/state-store/{key:path}``
+     - GET
+     - Assets.can_read
+     - Viewer
+   * - ``/api/v2/assets/{asset_id}/state-store/{key:path}``
+     - PUT
+     - Assets.can_edit
+     - Op
+   * - ``/api/v2/auth/login``
+     - GET
+     - None
+     - Public
+   * - ``/api/v2/auth/logout``
+     - GET
+     - None
+     - Public
+   * - ``/api/v2/backfills``
+     - GET
+     - DAGs.can_read, DAG Runs.can_read
+     - Viewer
+   * - ``/api/v2/backfills``
+     - POST
+     - DAGs.can_edit, DAG Runs.can_create
+     - User
+   * - ``/api/v2/backfills``
+     - PUT
+     - DAGs.can_edit, DAG Runs.can_edit
+     - User
+   * - ``/api/v2/config``
+     - GET
+     - Configurations.can_read
+     - Op
+   * - ``/api/v2/config/section/{section}/option/{option}``
+     - GET
+     - Configurations.can_read
+     - Op
+   * - ``/api/v2/connections``
+     - GET
+     - Connections.can_read
+     - Op
+   * - ``/api/v2/connections``
+     - PATCH
+     - Connections.can_read
+     - Op
+   * - ``/api/v2/connections``
+     - POST
+     - Connections.can_create
+     - Op
+   * - ``/api/v2/connections/defaults``
+     - POST
+     - Connections.can_create
+     - Op
+   * - ``/api/v2/connections/enqueue-test``
+     - GET
+     - None
+     - Public
+   * - ``/api/v2/connections/enqueue-test``
+     - POST
+     - None
+     - Public
+   * - ``/api/v2/connections/test``
+     - POST
+     - Connections.can_create
+     - Op
+   * - ``/api/v2/connections/{connection_id}``
+     - DELETE
+     - Connections.can_delete
+     - Op
+   * - ``/api/v2/connections/{connection_id}``
+     - GET
+     - Connections.can_read
+     - Op
+   * - ``/api/v2/connections/{connection_id}``
+     - PATCH
+     - Connections.can_edit
+     - Op
+   * - ``/api/v2/dagSources/{dag_id}``
+     - GET
+     - DAGs.can_read, DAG Code.can_read
+     - Viewer
+   * - ``/api/v2/dagStats``
+     - GET
+     - DAGs.can_read, DAG Runs.can_read
+     - Viewer
+   * - ``/api/v2/dagTags``
+     - GET
+     - DAGs.can_read
+     - Viewer
+   * - ``/api/v2/dagWarnings``
+     - GET
+     - DAGs.can_read, DAG Warnings.can_read
+     - Viewer
+   * - ``/api/v2/dags``
+     - GET
+     - DAGs.can_read
+     - Viewer
+   * - ``/api/v2/dags``
+     - PATCH
+     - DAGs.can_edit
+     - User
+   * - ``/api/v2/dags/{dag_id}``
+     - DELETE
+     - DAGs.can_delete
+     - User
+   * - ``/api/v2/dags/{dag_id}``
+     - GET
+     - DAGs.can_read
+     - Viewer
+   * - ``/api/v2/dags/{dag_id}``
+     - PATCH
+     - DAGs.can_edit
+     - User
+   * - ``/api/v2/dags/{dag_id}/assets/queuedEvents``
+     - DELETE
+     - Assets.can_delete
+     - Op
+   * - ``/api/v2/dags/{dag_id}/assets/queuedEvents``
+     - DELETE
+     - DAGs.can_edit
+     - User
+   * - ``/api/v2/dags/{dag_id}/assets/queuedEvents``
+     - GET
+     - Assets.can_read
+     - Viewer
+   * - ``/api/v2/dags/{dag_id}/assets/queuedEvents``
+     - GET
+     - DAGs.can_read
+     - Viewer
+   * - ``/api/v2/dags/{dag_id}/assets/{asset_id}/queuedEvents``
+     - DELETE
+     - Assets.can_delete
+     - Op
+   * - ``/api/v2/dags/{dag_id}/assets/{asset_id}/queuedEvents``
+     - DELETE
+     - DAGs.can_edit
+     - User
+   * - ``/api/v2/dags/{dag_id}/assets/{asset_id}/queuedEvents``
+     - GET
+     - Assets.can_read
+     - Viewer
+   * - ``/api/v2/dags/{dag_id}/assets/{asset_id}/queuedEvents``
+     - GET
+     - DAGs.can_read
+     - Viewer
+   * - ``/api/v2/dags/{dag_id}/clearDagRuns``
+     - POST
+     - DAGs.can_edit, DAG Runs.can_read
+     - User
+   * - ``/api/v2/dags/{dag_id}/clearPartitions``
+     - POST
+     - DAGs.can_edit, DAG Runs.can_edit
+     - User
+   * - ``/api/v2/dags/{dag_id}/clearTaskInstances``
+     - POST
+     - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit
+     - User
+   * - ``/api/v2/dags/{dag_id}/dagRuns``
+     - GET
+     - DAGs.can_read, DAG Runs.can_read
+     - Viewer
+   * - ``/api/v2/dags/{dag_id}/dagRuns``
+     - PATCH
+     - DAGs.can_edit, DAG Runs.can_read
+     - User
+   * - ``/api/v2/dags/{dag_id}/dagRuns``
+     - POST
+     - DAGs.can_edit, DAG Runs.can_create
+     - User
+   * - ``/api/v2/dags/{dag_id}/dagRuns/list``
+     - POST
+     - DAGs.can_read, DAG Runs.can_read
+     - Viewer
+   * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}``
+     - DELETE
+     - DAGs.can_edit, DAG Runs.can_delete
+     - User
+   * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}``
+     - GET
+     - DAGs.can_read, DAG Runs.can_read
+     - Viewer
+   * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}``
+     - PATCH
+     - DAGs.can_edit, DAG Runs.can_edit
+     - User
+   * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/clear``
+     - POST
+     - DAGs.can_edit, DAG Runs.can_edit
+     - User
+   * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/hitlDetails``
+     - GET
+     - DAGs.can_read
+     - Viewer
+   * - 
``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskGroupInstances/{group_id}``
+     - PATCH
+     - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit
+     - User
+   * - 
``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskGroupInstances/{group_id}/dry_run``
+     - PATCH
+     - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit
+     - User
+   * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances``
+     - GET
+     - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read
+     - Viewer
+   * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances``
+     - PATCH
+     - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit
+     - User
+   * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/list``
+     - POST
+     - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read
+     - Viewer
+   * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}``
+     - DELETE
+     - DAGs.can_edit, DAG Runs.can_delete, Task Instances.can_delete
+     - User
+   * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}``
+     - GET
+     - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read
+     - Viewer
+   * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}``
+     - PATCH
+     - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit
+     - User
+   * - 
``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/dependencies``
+     - GET
+     - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read
+     - Viewer
+   * - 
``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/dry_run``
+     - PATCH
+     - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit
+     - User
+   * - 
``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/externalLogUrl/{try_number}``
+     - GET
+     - DAGs.can_read, Task Logs.can_read
+     - Viewer
+   * - 
``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/links``
+     - GET
+     - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read
+     - Viewer
+   * - 
``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/listMapped``
+     - GET
+     - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read
+     - Viewer
+   * - 
``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/logs/{try_number}``
+     - GET
+     - DAGs.can_read, Task Logs.can_read
+     - Viewer
+   * - 
``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store``
+     - DELETE
+     - DAGs.can_edit, DAG Runs.can_delete, Task Instances.can_delete
+     - User
+   * - 
``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store``
+     - GET
+     - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read
+     - Viewer
+   * - 
``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store/{key:path}``
+     - DELETE
+     - DAGs.can_edit, DAG Runs.can_delete, Task Instances.can_delete
+     - User
+   * - 
``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store/{key:path}``
+     - GET
+     - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read
+     - Viewer
+   * - 
``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store/{key:path}``
+     - PATCH
+     - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit
+     - User
+   * - 
``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/state-store/{key:path}``
+     - PUT
+     - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit
+     - User
+   * - 
``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/tries``
+     - GET
+     - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read
+     - Viewer
+   * - 
``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/tries/{task_try_number}``
+     - GET
+     - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read
+     - Viewer
+   * - 
``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries``
+     - GET
+     - DAGs.can_read, XComs.can_read
+     - Viewer
+   * - 
``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries``
+     - POST
+     - DAGs.can_edit, XComs.can_create
+     - Op
+   * - 
``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries/{xcom_key:path}``
+     - DELETE
+     - DAGs.can_edit, XComs.can_delete
+     - Op
+   * - 
``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries/{xcom_key:path}``
+     - GET
+     - DAGs.can_read, XComs.can_read
+     - Viewer
+   * - 
``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/xcomEntries/{xcom_key:path}``
+     - PATCH
+     - DAGs.can_edit, XComs.can_edit
+     - Op
+   * - 
``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}``
+     - GET
+     - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read
+     - Viewer
+   * - 
``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}``
+     - PATCH
+     - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit
+     - User
+   * - 
``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}/dependencies``
+     - GET
+     - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read
+     - Viewer
+   * - 
``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}/dry_run``
+     - PATCH
+     - DAGs.can_edit, DAG Runs.can_edit, Task Instances.can_edit
+     - User
+   * - 
``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}/hitlDetails``
+     - GET
+     - DAGs.can_read
+     - Viewer
+   * - 
``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}/hitlDetails``
+     - PATCH
+     - DAGs.can_edit
+     - User
+   * - 
``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}/hitlDetails/tries/{try_number}``
+     - GET
+     - DAGs.can_read
+     - Viewer
+   * - 
``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}/tries``
+     - GET
+     - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read
+     - Viewer
+   * - 
``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/taskInstances/{task_id}/{map_index}/tries/{task_try_number}``
+     - GET
+     - DAGs.can_read, DAG Runs.can_read, Task Instances.can_read
+     - Viewer
+   * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/upstreamAssetEvents``
+     - GET
+     - Assets.can_read
+     - Viewer
+   * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/upstreamAssetEvents``
+     - GET
+     - DAGs.can_read, DAG Runs.can_read
+     - Viewer
+   * - ``/api/v2/dags/{dag_id}/dagRuns/{dag_run_id}/wait``
+     - GET
+     - DAGs.can_read, DAG Runs.can_read
+     - Viewer
+   * - ``/api/v2/dags/{dag_id}/dagVersions``
+     - GET
+     - DAGs.can_read, DAG Versions.can_read
+     - Viewer
+   * - ``/api/v2/dags/{dag_id}/dagVersions/{version_number}``
+     - GET
+     - DAGs.can_read, DAG Versions.can_read
+     - Viewer
+   * - ``/api/v2/dags/{dag_id}/details``
+     - GET
+     - DAGs.can_read
+     - Viewer
+   * - ``/api/v2/dags/{dag_id}/favorite``
+     - POST
+     - DAGs.can_read
+     - Viewer
+   * - ``/api/v2/dags/{dag_id}/tasks``
+     - GET
+     - DAGs.can_read, Task Instances.can_read
+     - Viewer
+   * - ``/api/v2/dags/{dag_id}/tasks/{task_id}``
+     - GET
+     - DAGs.can_read, Task Instances.can_read
+     - Viewer
+   * - ``/api/v2/dags/{dag_id}/unfavorite``
+     - POST
+     - DAGs.can_read
+     - Viewer
+   * - ``/api/v2/eventLogs``
+     - GET
+     - DAGs.can_read, Audit Logs.can_read
+     - Admin
+   * - ``/api/v2/eventLogs/{event_log_id}``
+     - GET
+     - DAGs.can_read, Audit Logs.can_read
+     - Admin
+   * - ``/api/v2/importErrors``
+     - GET
+     - ImportError.can_read
+     - Viewer
+   * - ``/api/v2/importErrors/{import_error_id}``
+     - GET
+     - ImportError.can_read
+     - Viewer
+   * - ``/api/v2/jobs``
+     - GET
+     - Jobs.can_read
+     - Viewer
+   * - ``/api/v2/monitor/health``
+     - GET
+     - None
+     - Public
+   * - ``/api/v2/parseDagFile/{file_token}``
+     - PUT
+     - DAGs.can_edit
+     - User
+   * - ``/api/v2/plugins``
+     - GET
+     - Plugins.can_read
+     - Op
+   * - ``/api/v2/plugins/importErrors``
+     - GET
+     - Plugins.can_read
+     - Op
+   * - ``/api/v2/pools``
+     - GET
+     - Pools.can_read
+     - Viewer
+   * - ``/api/v2/pools``
+     - PATCH
+     - Pools.can_read
+     - Viewer
+   * - ``/api/v2/pools``
+     - POST
+     - Pools.can_create
+     - Op
+   * - ``/api/v2/pools/{pool_name:path}``
+     - DELETE
+     - Pools.can_delete
+     - Op
+   * - ``/api/v2/pools/{pool_name:path}``
+     - GET
+     - Pools.can_read
+     - Viewer
+   * - ``/api/v2/pools/{pool_name:path}``
+     - PATCH
+     - Pools.can_edit
+     - Op
+   * - ``/api/v2/providers``
+     - GET
+     - Providers.can_read
+     - Op
+   * - ``/api/v2/variables``
+     - GET
+     - Variables.can_read
+     - Op
+   * - ``/api/v2/variables``
+     - PATCH
+     - Variables.can_read
+     - Op
+   * - ``/api/v2/variables``
+     - POST
+     - Variables.can_create
+     - Op
+   * - ``/api/v2/variables/{variable_key:path}``
+     - DELETE
+     - Variables.can_delete
+     - Op
+   * - ``/api/v2/variables/{variable_key:path}``
+     - GET
+     - Variables.can_read
+     - Op
+   * - ``/api/v2/variables/{variable_key:path}``
+     - PATCH
+     - Variables.can_edit
+     - Op
+   * - ``/api/v2/version``
+     - GET
+     - None
+     - Public
+   * - ``/fab/v1/permissions``
+     - GET
+     - Roles.can_read
+     - Admin
+   * - ``/fab/v1/roles``
+     - GET
+     - Roles.can_read
+     - Admin
+   * - ``/fab/v1/roles``
+     - POST
+     - Roles.can_create
+     - Admin
+   * - ``/fab/v1/roles/{name}``
+     - DELETE
+     - Roles.can_delete
+     - Admin
+   * - ``/fab/v1/roles/{name}``
+     - GET
+     - Roles.can_read
+     - Admin
+   * - ``/fab/v1/roles/{name}``
+     - PATCH
+     - Roles.can_edit
+     - Admin
+   * - ``/fab/v1/users``
+     - GET
+     - Users.can_read
+     - Admin
+   * - ``/fab/v1/users``
+     - POST
+     - Users.can_create
+     - Admin
+   * - ``/fab/v1/users/{username}``
+     - DELETE
+     - Users.can_delete
+     - Admin
+   * - ``/fab/v1/users/{username}``
+     - GET
+     - Users.can_read
+     - Admin
+   * - ``/fab/v1/users/{username}``
+     - PATCH
+     - Users.can_edit
+     - Admin
+
+.. END GENERATED PERMISSIONS TABLE
 
 
 ====================================== 
======================================================================= 
============
diff --git a/scripts/ci/prek/fab_permissions_doc.py 
b/scripts/ci/prek/fab_permissions_doc.py
new file mode 100755
index 00000000000..53e81dba467
--- /dev/null
+++ b/scripts/ci/prek/fab_permissions_doc.py
@@ -0,0 +1,416 @@
+#!/usr/bin/env python
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""
+Generate the FAB auth manager's REST API permission table.
+
+``extract_permissions.py`` produces the auth-manager-agnostic reference table
+(``Resource`` + ``Required permission``) for airflow-core. The FAB provider
+documents the same endpoints in its own vocabulary -- concrete permission names
+such as ``DAGs.can_read`` plus the minimum built-in role that grants them --
+and that table is still maintained by hand, so it drifts.
+
+This module reuses ``extract_permissions.py``'s parser and renders the FAB view
+from the same entries, so both tables come from one source of truth.
+
+Like ``extract_permissions.py`` this runs entirely statically: route files, the
+FAB resource maps and the FAB role definitions are all read with Python's AST
+parser, so no Airflow or FAB import is required.
+"""
+
+from __future__ import annotations
+
+import ast
+import pathlib
+import sys
+
+from extract_permissions import (
+    PUBLIC_ROUTES_DIR,
+    PermissionEntry,
+    extract_all_permissions,
+)
+
+REPO_ROOT = pathlib.Path(__file__).resolve().parents[3]
+FAB_SRC = REPO_ROOT / "providers/fab/src/airflow/providers/fab"
+PERMISSIONS_PY = FAB_SRC / "www/security/permissions.py"
+AUTH_MANAGER_PY = FAB_SRC / "auth_manager/fab_auth_manager.py"
+SECURITY_MANAGER_PY = FAB_SRC / "auth_manager/security_manager/override.py"
+# The table is written in place, between markers, inside the access-control 
page.
+# A separate fragment file cannot live here: every ``.rst`` under the docs 
tree is
+# scanned as a document, so a title-less include fragment trips the glob 
toctree.
+OUTPUT_RST = REPO_ROOT / "providers/fab/docs/auth-manager/access-control.rst"
+BEGIN_MARKER = ".. BEGIN GENERATED PERMISSIONS TABLE"
+END_MARKER = ".. END GENERATED PERMISSIONS TABLE"
+
+# FAB serves its own user and role management endpoints from a separate 
router, using
+# ``requires_fab_custom_view`` rather than the core ``requires_access_*`` 
helpers. They
+# are real endpoints and belong in this table, so they are parsed here as well.
+FAB_ROUTES_DIR = FAB_SRC / "auth_manager/api_fastapi/routes"
+FAB_ROUTER_PY = FAB_ROUTES_DIR / "router.py"
+
+# HTTP verb -> FAB action. PATCH and PUT are both edits.
+_METHOD_TO_ACTION = {
+    "GET": "can_read",
+    "POST": "can_create",
+    "PUT": "can_edit",
+    "PATCH": "can_edit",
+    "DELETE": "can_delete",
+}
+
+# Built-in roles, ordered from least to most privileged. ``ROLE_CONFIGS`` 
builds
+# each one as a superset of the previous, so the minimum role that grants a set
+# of permissions is the first entry here whose permissions cover them all.
+_ROLE_ORDER = ("Public", "Viewer", "User", "Op", "Admin")
+
+
+def _resolve_str_constants(tree: ast.Module) -> dict[str, str]:
+    """Collect module-level ``NAME = "value"`` string assignments."""
+    out: dict[str, str] = {}
+    for node in tree.body:
+        if isinstance(node, ast.Assign) and isinstance(node.value, 
ast.Constant):
+            if isinstance(node.value.value, str):
+                for target in node.targets:
+                    if isinstance(target, ast.Name):
+                        out[target.id] = node.value.value
+    return out
+
+
+def _parse(path: pathlib.Path) -> ast.Module:
+    return ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
+
+
+def _resource_display_names() -> dict[str, str]:
+    """``RESOURCE_DAG`` -> ``DAGs``, straight from ``permissions.py``."""
+    return _resolve_str_constants(_parse(PERMISSIONS_PY))
+
+
+def _dict_literal(tree: ast.Module, name: str) -> ast.Dict | None:
+    for node in tree.body:
+        if isinstance(node, ast.Assign):
+            for target in node.targets:
+                if isinstance(target, ast.Name) and target.id == name:
+                    if isinstance(node.value, ast.Dict):
+                        return node.value
+        if isinstance(node, ast.AnnAssign):
+            if isinstance(node.target, ast.Name) and node.target.id == name:
+                if isinstance(node.value, ast.Dict):
+                    return node.value
+    return None
+
+
+def _enum_member(node: ast.expr) -> str | None:
+    """``DagAccessEntity.TASK_INSTANCE`` -> ``TASK_INSTANCE``."""
+    if isinstance(node, ast.Attribute):
+        return node.attr
+    return None
+
+
+def _const_names(node: ast.expr) -> tuple[str, ...]:
+    """A ``RESOURCE_X`` name, or a tuple of them, as their identifier 
strings."""
+    if isinstance(node, ast.Name):
+        return (node.id,)
+    if isinstance(node, ast.Tuple):
+        return tuple(e.id for e in node.elts if isinstance(e, ast.Name))
+    return ()
+
+
+def _entity_maps() -> tuple[dict[str, tuple[str, ...]], dict[str, str]]:
+    """Read FAB's two mapping dicts rather than restating them here.
+
+    Restating them would create exactly the second source of truth this
+    generator exists to remove.
+    """
+    tree = _parse(AUTH_MANAGER_PY)
+
+    dag_map: dict[str, tuple[str, ...]] = {}
+    node = _dict_literal(tree, "_MAP_DAG_ACCESS_ENTITY_TO_FAB_RESOURCE_TYPE")
+    if node is not None:
+        for key, value in zip(node.keys, node.values):
+            member = _enum_member(key) if key is not None else None
+            if member:
+                dag_map[member] = _const_names(value)
+
+    view_map: dict[str, str] = {}
+    node = _dict_literal(tree, "_MAP_ACCESS_VIEW_TO_FAB_RESOURCE_TYPE")
+    if node is not None:
+        for key, value in zip(node.keys, node.values):
+            member = _enum_member(key) if key is not None else None
+            names = _const_names(value)
+            if member and names:
+                view_map[member] = names[0]
+
+    return dag_map, view_map
+
+
+def _role_permissions() -> dict[str, set[tuple[str, str]]]:
+    """Build ``{role: {(action, resource), ...}}`` from ``override.py``.
+
+    ``VIEWER_PERMISSIONS`` and friends are class-level list literals of
+    ``(ACTION_*, RESOURCE_*)`` tuples; ``ROLE_CONFIGS`` then concatenates them.
+    Both are parsed statically.
+    """
+    tree = _parse(SECURITY_MANAGER_PY)
+    consts = _resolve_str_constants(tree)
+
+    groups: dict[str, set[tuple[str, str]]] = {}
+    for node in ast.walk(tree):
+        if not isinstance(node, ast.Assign) or not isinstance(node.value, 
ast.List):
+            continue
+        for target in node.targets:
+            if not isinstance(target, ast.Name) or not 
target.id.endswith("_PERMISSIONS"):
+                continue
+            pairs: set[tuple[str, str]] = set()
+            for elt in node.value.elts:
+                if not isinstance(elt, ast.Tuple) or len(elt.elts) != 2:
+                    continue
+                action = _name_of(elt.elts[0])
+                resource = _name_of(elt.elts[1])
+                if action and resource:
+                    pairs.add((action, resource))
+            groups[target.id] = pairs
+
+    # ROLE_CONFIGS is cumulative; rebuild that stacking explicitly.
+    roles: dict[str, set[tuple[str, str]]] = {"Public": set()}
+    roles["Viewer"] = set(groups.get("VIEWER_PERMISSIONS", set()))
+    roles["User"] = roles["Viewer"] | groups.get("USER_PERMISSIONS", set())
+    roles["Op"] = roles["User"] | groups.get("OP_PERMISSIONS", set())
+    roles["Admin"] = roles["Op"] | groups.get("ADMIN_PERMISSIONS", set())
+
+    # Resolve ACTION_*/RESOURCE_* identifiers to their string values.
+    perm_consts = _resolve_str_constants(_parse(PERMISSIONS_PY))
+    perm_consts.update(consts)
+    resolved: dict[str, set[tuple[str, str]]] = {}
+    for role, pairs in roles.items():
+        resolved[role] = {(perm_consts.get(a, a), perm_consts.get(r, r)) for 
a, r in pairs}
+    return resolved
+
+
+def _name_of(node: ast.expr) -> str | None:
+    if isinstance(node, ast.Name):
+        return node.id
+    if isinstance(node, ast.Attribute):
+        return node.attr
+    return None
+
+
+def fab_permissions_for(entry: PermissionEntry) -> list[str]:
+    """Translate one generic entry into FAB permission strings."""
+    display = _resource_display_names()
+    dag_map, view_map = _entity_maps()
+
+    if entry.resource == "Public":
+        return []
+
+    if entry.resource == "View" or entry.resource.startswith("View."):
+        # ``_build_resource_label`` renders these as ``View.PLUGINS``; the 
view name is
+        # also carried in ``required_permission``.
+        view_name = entry.resource.split(".", 1)[1] if "." in entry.resource 
else entry.required_permission
+        const = view_map.get(view_name)
+        if const is None:
+            return []
+        return [f"{display.get(const, const)}.can_read"]
+
+    # ``required_permission`` is the method passed to ``requires_access_*``, 
which is
+    # not always the route's HTTP verb: ``/clearTaskInstances`` is a POST 
route that
+    # authorizes with PUT. The authorization method is the one that decides 
the action.
+    auth_method = entry.required_permission
+    action = _METHOD_TO_ACTION.get(auth_method, "can_read")
+
+    if entry.resource.startswith("DAG."):
+        entity = entry.resource.split(".", 1)[1]
+        consts = dag_map.get(entity, ())
+        perms = [f"{display.get(c, c)}.{action}" for c in consts]
+        # A sub-entity check always runs the base Dag check first: 
``is_authorized_dag``
+        # uses GET for a read and PUT for anything else, so the base 
requirement is
+        # can_read on GET and can_edit otherwise -- it is present on GET 
routes too.
+        base = "can_read" if auth_method == "GET" else "can_edit"
+        dag_name = display.get("RESOURCE_DAG", "DAGs")
+        return [f"{dag_name}.{base}", *perms]
+
+    const_name = f"RESOURCE_{_RESOURCE_TO_CONST.get(entry.resource, 
entry.resource.upper())}"
+    return [f"{display.get(const_name, entry.resource)}.{action}"]
+
+
+# Generic resource label -> the RESOURCE_* suffix in permissions.py.
+_RESOURCE_TO_CONST = {
+    "DAG": "DAG",
+    "Pool": "POOL",
+    "Connection": "CONNECTION",
+    "Configuration": "CONFIG",
+    "Variable": "VARIABLE",
+    "Asset": "ASSET",
+    "AssetAlias": "ASSET_ALIAS",
+}
+
+
+def minimum_role(perms: list[str]) -> str:
+    """First built-in role whose permissions cover every required 
permission."""
+    if not perms:
+        return "Public"
+    roles = _role_permissions()
+    required = set()
+    for perm in perms:
+        resource, _, action = perm.rpartition(".")
+        required.add((action, resource))
+    for role in _ROLE_ORDER:
+        if required <= roles.get(role, set()):
+            return role
+    return "Admin"
+
+
+def _fab_router_prefix() -> str:
+    """Read ``FAB_AUTH_PREFIX`` from router.py rather than hardcoding it."""
+    return 
_resolve_str_constants(_parse(FAB_ROUTER_PY)).get("FAB_AUTH_PREFIX", "")
+
+
+def _fab_route_entries() -> list[tuple[str, str, str, str]]:
+    """Parse FAB's own routes into ``(path, http_method, permissions, role)`` 
rows.
+
+    ``requires_fab_custom_view(method, resource)`` names its resource 
directly, so no
+    entity expansion or base-Dag rule applies here -- unlike the core helpers.
+    """
+    display = _resource_display_names()
+    prefix = _fab_router_prefix()
+    rows: list[tuple[str, str, str, str]] = []
+
+    for route_file in sorted(FAB_ROUTES_DIR.glob("*.py")):
+        if route_file.name in {"__init__.py", "router.py"}:
+            continue
+        for node in ast.walk(_parse(route_file)):
+            if not isinstance(node, ast.FunctionDef):
+                continue
+            for deco in node.decorator_list:
+                if not isinstance(deco, ast.Call):
+                    continue
+                verb = _name_of(deco.func)
+                if verb is None or verb.upper() not in _METHOD_TO_ACTION:
+                    continue
+                if not deco.args or not isinstance(deco.args[0], ast.Constant):
+                    continue
+                path = deco.args[0].value
+                if not isinstance(path, str):
+                    continue
+
+                perms: list[str] = []
+                for kw in deco.keywords:
+                    if kw.arg != "dependencies" or not isinstance(kw.value, 
ast.List):
+                        continue
+                    for dep in kw.value.elts:
+                        if not isinstance(dep, ast.Call) or not dep.args:
+                            continue
+                        inner = dep.args[0]
+                        if not isinstance(inner, ast.Call):
+                            continue
+                        if _name_of(inner.func) != "requires_fab_custom_view":
+                            continue
+                        if len(inner.args) < 2 or not 
isinstance(inner.args[0], ast.Constant):
+                            continue
+                        auth_method = inner.args[0].value
+                        const = _name_of(inner.args[1])
+                        if not const or not isinstance(auth_method, str):
+                            continue
+                        action = _METHOD_TO_ACTION.get(auth_method, "can_read")
+                        perms.append(f"{display.get(const, const)}.{action}")
+
+                if perms:
+                    rows.append((f"{prefix}{path}", verb.upper(), ", 
".join(perms), minimum_role(perms)))
+    return rows
+
+
+def render_rst(entries: list[PermissionEntry]) -> str:
+    rows = []
+    for entry in sorted(entries):
+        perms = fab_permissions_for(entry)
+        rows.append(
+            (
+                entry.full_path,
+                entry.http_method,
+                ", ".join(perms) if perms else "None",
+                minimum_role(perms),
+            )
+        )
+
+    rows.extend(_fab_route_entries())
+    rows.sort()
+
+    head = ("Endpoint", "Method", "Permissions", "Minimum role")
+    lines = [
+        BEGIN_MARKER,
+        "",
+        ".. THE TABLE BELOW IS AUTO-GENERATED. DO NOT EDIT IT MANUALLY.",
+        "   Regenerate with:  python scripts/ci/prek/fab_permissions_doc.py",
+        "   Trigger:          prek run generate-fab-permissions-doc 
--all-files",
+        "",
+        ".. list-table:: Stable REST API permissions (FAB auth manager)",
+        "   :header-rows: 1",
+        "   :widths: 45 8 32 15",
+        "",
+        f"   * - {head[0]}",
+        f"     - {head[1]}",
+        f"     - {head[2]}",
+        f"     - {head[3]}",
+    ]
+    for row_path, row_method, row_perms, row_role in rows:
+        lines += [
+            f"   * - ``{row_path}``",
+            f"     - {row_method}",
+            f"     - {row_perms}",
+            f"     - {row_role}",
+        ]
+    lines += ["", END_MARKER]
+    return "\n".join(lines) + "\n"
+
+
+def main(argv: list[str] | None = None) -> int:
+    import argparse
+
+    parser = argparse.ArgumentParser(description="Generate the FAB API 
permission table.")
+    parser.add_argument("--check", action="store_true", help="Exit 1 if the 
file on disk is stale.")
+    parser.add_argument("--print", dest="print_only", action="store_true", 
help="Print instead of write.")
+    args = parser.parse_args(argv)
+
+    entries = extract_all_permissions(PUBLIC_ROUTES_DIR)
+    table = render_rst(entries)
+
+    if args.print_only:
+        print(table)
+        return 0
+
+    current = OUTPUT_RST.read_text(encoding="utf-8")
+    start = current.find(BEGIN_MARKER)
+    end = current.find(END_MARKER)
+    if start == -1 or end == -1:
+        print(f"[FAIL] {OUTPUT_RST} is missing the generated-table markers.", 
file=sys.stderr)
+        return 1
+    updated = current[:start] + table + current[end + len(END_MARKER) + 1 :]
+
+    if args.check:
+        if current != updated:
+            print(
+                f"[FAIL] {OUTPUT_RST} is stale. Run: python 
{pathlib.Path(__file__).name}",
+                file=sys.stderr,
+            )
+            return 1
+        print(f"[OK] {OUTPUT_RST} is up to date.")
+        return 0
+
+    OUTPUT_RST.write_text(updated, encoding="utf-8")
+    return 0
+
+
+if __name__ == "__main__":
+    sys.exit(main())

Reply via email to