[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6ACB55GGWPTWFKM3BVQS4W5":3,"skill-same:01M6ACB55GGWPTWFKM3BVQS4W5":76},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":41,"linkOnly":42,"markdownContent":43,"markdownBody":44,"frontMatter":45,"refName":47,"commitSha":48,"skillDirectory":49,"forksCount":50,"repositoryHtmlUrl":51,"repositoryHomepageUrl":52,"ownerHtmlUrl":53,"ownerBlogUrl":54,"ownerDescription":6,"updatedAt":55,"install":56,"resources":69},"01M6ACB55GGWPTWFKM3BVQS4W5","upgrade-fab-provider",null,"Upgrade the pinned Flask-AppBuilder (FAB) dependency in the Apache Airflow FAB provider (`providers\u002Ffab\u002F`). Bumps the exact `flask-appbuilder==` pin and its mirror constant, regenerates `uv.lock`, drives the `test_fab_alignment.py` drift tripwire, reviews the vendored security-manager `override.py` against the new upstream FAB, and conditionally re-vendors static assets \u002F DB migrations. Use when asked to \"upgrade FAB\", \"bump flask-appbuilder\", or move the FAB provider to a newer Flask-AppBuilder release.","apache\u002Fairflow","apache","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F47359?v=4","01M6AC83E6K4FDKK7HZ8WNMQKE","The Apache Software Foundation","01M6ACADDFF6RHG1DBAJXFG7E5",47452,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Fapache\u002Fairflow\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fupgrade-fab-provider\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,9,38,39,40],"upgrade fab provider","upgrade","provider","alignment","new","when","static","lock","reviews","re","move","db","drift","security","uv","bump","dependency","tripwire","apache airflow","airflow","release","test","2026-11-10 10:12:30",false,"---\nname: upgrade-fab-provider\ndescription: >\n  Upgrade the pinned Flask-AppBuilder (FAB) dependency in the Apache Airflow\n  FAB provider (`providers\u002Ffab\u002F`). Bumps the exact `flask-appbuilder==` pin and\n  its mirror constant, regenerates `uv.lock`, drives the `test_fab_alignment.py`\n  drift tripwire, reviews the vendored security-manager `override.py` against the\n  new upstream FAB, and conditionally re-vendors static assets \u002F DB migrations.\n  Use when asked to \"upgrade FAB\", \"bump flask-appbuilder\", or move the FAB\n  provider to a newer Flask-AppBuilder release.\nlicense: Apache-2.0\n---\n\n\u003C!-- SPDX-License-Identifier: Apache-2.0\n     https:\u002F\u002Fwww.apache.org\u002Flicenses\u002FLICENSE-2.0 -->\n\n# upgrade-fab-provider\n\nAirflow's FAB provider is **tightly coupled** to a specific Flask-AppBuilder\nrelease because it vendors-in and subclasses large parts of FAB's security\nmanager. A version bump is therefore never \"just change the pin\" — it must be\nreconciled against the vendored code, and that reconciliation is enforced by a\npytest **alignment test**, not a prek hook.\n\nThe canonical reference for a real bump is PR **#66841** (\"Bump flask-appbuilder\nto 5.2.1 and mirror new auth event hooks\") — commit `c72b6613fd`. Read its diff\nfirst when in doubt: `git show c72b6613fd`.\n\n## The coupling — why this is not a one-line change\n\n- `providers\u002Ffab\u002Fpyproject.toml:75-80` explains it: Airflow vendored FAB's\n  security-manager code into\n  `providers\u002Ffab\u002Fsrc\u002Fairflow\u002Fproviders\u002Ffab\u002Fauth_manager\u002Fsecurity_manager\u002Foverride.py`\n  (~2700 lines) as `FabAirflowSecurityManagerOverride`. Every bump must review\n  that class against upstream FAB for new \u002F changed \u002F removed methods.\n- `test_fab_alignment.py` mechanically detects drift between the **installed**\n  FAB package and the vendored override, and **fails CI** until the developer\n  reconciles it.\n\n## Inputs\n\n- **Target version** — the FAB version to move to. If not given, use the latest\n  release on PyPI (`https:\u002F\u002Fpypi.org\u002Fpypi\u002Fflask-appbuilder\u002Fjson` → `info.version`).\n  Confirm the target with the user if it is a **major or minor** bump (higher\n  reconciliation risk); a **patch** bump can proceed.\n\n## The files a bump touches\n\n**Always:**\n\n1. `providers\u002Ffab\u002Fpyproject.toml` — line ~80, the `flask-appbuilder==X.Y.Z` pin\n   (the **only** real dependency pin in the repo).\n2. `providers\u002Ffab\u002Ftests\u002Funit\u002Ffab\u002Fauth_manager\u002Fsecurity_manager\u002Ftest_fab_alignment.py`\n   — `EXPECTED_FAB_VERSION = \"X.Y.Z\"` (line ~43). Must move in lockstep with the pin.\n3. `providers\u002Ffab\u002Fdocs\u002Findex.rst` — the dependency table row\n   ``` ``flask-appbuilder``   ``==X.Y.Z`` ``` (line ~114).\n4. `providers\u002Ffab\u002FREADME.rst` — the Requirements table row (line ~60). **Do\n   not hand-edit** — it is auto-generated. Regenerate it from the bumped\n   `pyproject.toml` with the `sync-provider-readme` prek hook (Step 8); the hook\n   re-renders the table whenever `pyproject.toml` changes. (Pre-existing bumps\n   that predate this hook left it to release-time regeneration; today the hook is\n   per-commit, so CI flags the drift — run it.)\n5. `uv.lock` — regenerated (see Step 4 for the pinned-uv caveat).\n\n**Conditionally:**\n\n6. `providers\u002Ffab\u002Fsrc\u002Fairflow\u002Fproviders\u002Ffab\u002Fauth_manager\u002Fsecurity_manager\u002Foverride.py`\n   — transplant any relevant upstream FAB changes (new auth hooks, changed\n   signatures, ported fixes). The reference bump added +37 lines here. **Note:**\n   a green alignment test does not prove the transplant is unnecessary — a\n   security fix may live inside a method Airflow vendors (see the 5.2.2 worked\n   example below).\n7. `providers\u002Ffab\u002Fsrc\u002Fairflow\u002Fproviders\u002Ffab\u002Fwww\u002F**` + `providers\u002Ffab\u002Fwww-hash.txt`\n   — only if the new FAB ships changed static assets \u002F templates that are\n   re-vendored (see Step 6).\n8. `providers\u002Ffab\u002Fsrc\u002Fairflow\u002Fproviders\u002Ffab\u002Fmigrations\u002Fversions\u002F**` — only if the\n   new FAB version ships DB migrations (see Step 7).\n\n**Never:** no newsfragment, and do **not** hand-edit `providers\u002Ffab\u002Fdocs\u002Fchangelog.rst`\n— providers are released from `main` and the release manager regenerates the\nchangelog from `git log` (per `providers\u002FAGENTS.md`). The commit subject is the\nchangelog entry.\n\n## Procedure\n\n### Step 1 — Determine the target version and current state\n\n- Read the current pin: `grep flask-appbuilder== providers\u002Ffab\u002Fpyproject.toml`.\n- Resolve the target (PyPI latest, or the user's requested version).\n- Classify the jump: patch \u002F minor \u002F major. For minor\u002Fmajor, skim the FAB\n  release notes (`https:\u002F\u002Fgithub.com\u002Fdpgaspar\u002FFlask-AppBuilder\u002Freleases`) for\n  security-manager \u002F model \u002F template changes before editing.\n\n### Step 2 — Bump the three source pins\n\nEdit in lockstep:\n\n- `providers\u002Ffab\u002Fpyproject.toml` — the `flask-appbuilder==X.Y.Z` line (keep the\n  trailing `# Whenever updating the version, run test_fab_alignment.py to verify.`\n  comment).\n- `test_fab_alignment.py` — `EXPECTED_FAB_VERSION = \"X.Y.Z\"`. It is the tripwire;\n  editing it now is fine — you will keep re-running the test until it and the\n  other three tests pass.\n- `providers\u002Ffab\u002Fdocs\u002Findex.rst` — the dependency-table `==X.Y.Z` row.\n\n### Step 3 — Install the new FAB into the provider venv\n\n`uv --project providers\u002Ffab sync` installs the new pin. Confirm the installed\nversion:\n\n```bash\nuv run --project providers\u002Ffab python -c \\\n  \"import importlib.metadata as m; print(m.version('flask-appbuilder'))\"\n```\n\n### Step 4 — Regenerate uv.lock (pinned uv!)\n\nThe lock **must** be regenerated with the repo's pinned uv version, or it drifts\nhundreds of unrelated marker lines:\n\n```bash\nAIRFLOW_UV_VERSION=$(grep -oE 'AIRFLOW_UV_VERSION=[0-9.]+' Dockerfile.ci | head -1 | cut -d= -f2)\nuvx --from uv==$AIRFLOW_UV_VERSION uv lock\n```\n\nIf a conflict is irrecoverable, delete `uv.lock` and re-run `uv lock` with the\npinned version. Confirm the diff is limited to the FAB bump, not a wholesale\nmarker rewrite.\n\n### Step 5 — Run the alignment test and reconcile override.py\n\n```bash\nuv run --project providers\u002Ffab pytest \\\n  providers\u002Ffab\u002Ftests\u002Funit\u002Ffab\u002Fauth_manager\u002Fsecurity_manager\u002Ftest_fab_alignment.py -xvs\n```\n\nUnder the host sandbox the test needs a writable `AIRFLOW_HOME` and the\nrerun-failures socket disabled — if it errors on a socket `bind` or on\n`~\u002Fairflow`, run it as:\n\n```bash\nAIRFLOW_HOME=\"$TMPDIR\u002Ffab_home\" uv run --project providers\u002Ffab pytest \\\n  providers\u002Ffab\u002Ftests\u002F...\u002Ftest_fab_alignment.py -q -p no:rerunfailures\n```\n\nThe four tests and how to fix each:\n\n1. **`test_fab_version_matches_expected`** — trips on the version mismatch. It\n   passes once `EXPECTED_FAB_VERSION` == installed version, but only after you\n   have done the review below.\n2. **`test_no_unaudited_fab_methods`** — a new FAB public method exists that is\n   neither implemented in `override.py` nor listed in `AUDITED_EXCLUSIONS`.\n   Fix: either implement\u002Foverride it in `override.py`, or add it to\n   `AUDITED_EXCLUSIONS` **with a justification comment**.\n3. **`test_no_stale_exclusions`** — `AUDITED_EXCLUSIONS` lists a method the new\n   FAB no longer has. Fix: remove that entry.\n4. **`test_shared_method_signatures_compatible`** — FAB changed a method\n   signature (new required param). Fix: update the `override.py` signature, or\n   add to `KNOWN_SIGNATURE_DEVIATIONS` if the divergence is intentional.\n\n**The manual review that the test cannot fully automate:** diff the vendored\n`override.py` against the new FAB's `flask_appbuilder\u002Fsecurity\u002Fsqla\u002Fmanager.py`\nand `BaseSecurityManager` and **transplant behavioural changes** (bug fixes, new\nauth event hooks), not just signatures — the test only checks method *presence*\nand *required params*. Locate the installed source:\n\n```bash\nuv run --project providers\u002Ffab python -c \\\n  \"import flask_appbuilder.security.sqla.manager as m; print(m.__file__)\"\n```\n\n### Step 6 — Re-vendor static assets \u002F templates (only if changed)\n\nIf the new FAB changed frontend assets that Airflow vendors under\n`providers\u002Ffab\u002Fsrc\u002Fairflow\u002Fproviders\u002Ffab\u002Fwww\u002F` (templates in\n`templates\u002Fappbuilder\u002F`, static JS\u002FCSS), re-vendor them, then regenerate the\nfingerprint:\n\n```bash\nprek run compile-fab-assets --all-files\n```\n\nThis runs `scripts\u002Fci\u002Fprek\u002Fcompile_provider_assets.py fab` (pnpm build over\n`www\u002F`) and rewrites `providers\u002Ffab\u002Fwww-hash.txt`. A patch bump usually does\n**not** touch assets — skip this step unless FAB's templates\u002Fstatic changed.\nCommit the regenerated `www-hash.txt` if it changed.\n\n### Step 7 — DB migrations (only if FAB ships them)\n\nIf the new FAB adds\u002Fchanges security-model tables, add the corresponding\nmigration under `providers\u002Ffab\u002Fsrc\u002Fairflow\u002Fproviders\u002Ffab\u002Fmigrations\u002Fversions\u002F`\nand run:\n\n```bash\nprek run update-migration-references-fab check-revision-heads-map-fab --all-files\n```\n\nPatch bumps normally have no migrations — skip unless the release notes mention\nschema changes.\n\n### Step 8 — Static checks + tests\n\n```bash\nprek run --from-ref main --stage pre-commit\nuv run --project providers\u002Ffab pytest providers\u002Ffab\u002Ftests\u002Funit\u002Ffab\u002Fauth_manager -xvs\n```\n\nThe pre-commit stage runs `sync-provider-readme` (regenerating `README.rst`) and\nother FAB hooks. Re-run the alignment test until all four tests pass. The full\nprovider suite is `breeze testing providers-tests --test-type \"Providers[fab]\"`.\n\n### Step 9 — Self-review and commit\n\n- `git diff main...HEAD` — verify only the intended files changed, and `uv.lock`\n  is a clean FAB-scoped diff.\n- Commit subject in imperative mood, plain prose, **no** Conventional-Commits\n  prefix, e.g. `Bump flask-appbuilder to X.Y.Z in FAB provider`. Body explains\n  *why* (what upstream changes were mirrored), not what.\n- No newsfragment, no changelog edit (provider release manager regenerates from\n  git log).\n- Prepend your PR to the history list in `providers\u002Ffab\u002FCONTRIBUTING.rst` so the\n  FAB-upgrade record stays current.\n- Push to `origin` and open the PR per the repo's PR conventions.\n\n## Gotchas\n\n- **`EXPECTED_FAB_VERSION` is a second pin.** Forgetting it makes\n  `test_fab_alignment.py` fail even when everything else is correct.\n- **`uv.lock` marker drift.** Always use the pinned uv (Step 4) — a bare\n  `uv lock` rewrites hundreds of environment-marker lines and buries the real diff.\n- **The alignment test uses AST, not import**, to read FAB's `SecurityManager`\n  (to avoid SQLAlchemy model-registry collisions with Airflow's vendored models).\n  A green test proves *structural* alignment; it does **not** prove behavioural\n  parity — Step 5's manual transplant review is still required.\n- **`docs\u002Findex.rst` dependency table** may look auto-generated but the reference\n  PR edited it by hand; if a docs-regen prek hook rewrites it, let the hook win.\n- **`README.rst` is generated, but the sync hook is per-commit.** Don't hand-edit\n  it; run `prek run sync-provider-readme` (or the full pre-commit stage) after\n  bumping `pyproject.toml` — CI fails on the drift otherwise.\n- **Don't touch `providers\u002Ffab\u002Fdocs\u002Fupgrading.rst`** — that is end-user guidance\n  for upgrading the *provider package* in a deployment, not the developer bump\n  workflow.\n\n## Worked example — 5.2.1 to 5.2.2 (patch)\n\nA patch bump that needed **no `override.py` change**, but only after a real\nbehavioural review — the green alignment test alone was not sufficient evidence:\n\n- FAB 5.2.2 shipped three security-manager fixes: LDAP search-filter escaping,\n  OAuth email-allowlist regex anchoring (`email + \"$\"`), and API-login provider\n  validation.\n- All three touch methods Airflow *vendors* (`_search_ldap`, `auth_user_ldap`,\n  `auth_user_oauth`) — so the alignment test passing did **not** mean \"nothing to\n  do\". Each had to be checked by hand:\n  - **LDAP escaping** — Airflow's vendored `_search_ldap` **already** escapes via\n    `ldap.filter.escape_filter_chars` (and adds filter-parenthesis validation); it\n    was ahead of FAB. No transplant.\n  - **OAuth allowlist anchoring** — the match lives in FAB's `AuthOAuthView`\n    (`views.py`), and Airflow's `CustomAuthOAuthView.oauth_authorized` delegates\n    via `super().oauth_authorized()`, so the fix is inherited from the installed\n    FAB 5.2.2. No transplant.\n  - **API-login validation \u002F Azure-JWT warning \u002F uuid4** — in FAB core Airflow\n    doesn't vendor. Inherited.\n- Net change set: `pyproject.toml`, `test_fab_alignment.py`, `docs\u002Findex.rst`,\n  `README.rst` (via hook), `uv.lock`. Commit: `Bump flask-appbuilder to 5.2.2 in\n  FAB provider`.\n\nThe lesson the skill encodes: **for every security\u002Fbehavioural fix in the FAB\nrelease notes, locate the method and check whether Airflow vendors it** — the\nalignment test guards structure, you guard behaviour.\n","\u003C!-- SPDX-License-Identifier: Apache-2.0\n     https:\u002F\u002Fwww.apache.org\u002Flicenses\u002FLICENSE-2.0 -->\n\n# upgrade-fab-provider\n\nAirflow's FAB provider is **tightly coupled** to a specific Flask-AppBuilder\nrelease because it vendors-in and subclasses large parts of FAB's security\nmanager. A version bump is therefore never \"just change the pin\" — it must be\nreconciled against the vendored code, and that reconciliation is enforced by a\npytest **alignment test**, not a prek hook.\n\nThe canonical reference for a real bump is PR **#66841** (\"Bump flask-appbuilder\nto 5.2.1 and mirror new auth event hooks\") — commit `c72b6613fd`. Read its diff\nfirst when in doubt: `git show c72b6613fd`.\n\n## The coupling — why this is not a one-line change\n\n- `providers\u002Ffab\u002Fpyproject.toml:75-80` explains it: Airflow vendored FAB's\n  security-manager code into\n  `providers\u002Ffab\u002Fsrc\u002Fairflow\u002Fproviders\u002Ffab\u002Fauth_manager\u002Fsecurity_manager\u002Foverride.py`\n  (~2700 lines) as `FabAirflowSecurityManagerOverride`. Every bump must review\n  that class against upstream FAB for new \u002F changed \u002F removed methods.\n- `test_fab_alignment.py` mechanically detects drift between the **installed**\n  FAB package and the vendored override, and **fails CI** until the developer\n  reconciles it.\n\n## Inputs\n\n- **Target version** — the FAB version to move to. If not given, use the latest\n  release on PyPI (`https:\u002F\u002Fpypi.org\u002Fpypi\u002Fflask-appbuilder\u002Fjson` → `info.version`).\n  Confirm the target with the user if it is a **major or minor** bump (higher\n  reconciliation risk); a **patch** bump can proceed.\n\n## The files a bump touches\n\n**Always:**\n\n1. `providers\u002Ffab\u002Fpyproject.toml` — line ~80, the `flask-appbuilder==X.Y.Z` pin\n   (the **only** real dependency pin in the repo).\n2. `providers\u002Ffab\u002Ftests\u002Funit\u002Ffab\u002Fauth_manager\u002Fsecurity_manager\u002Ftest_fab_alignment.py`\n   — `EXPECTED_FAB_VERSION = \"X.Y.Z\"` (line ~43). Must move in lockstep with the pin.\n3. `providers\u002Ffab\u002Fdocs\u002Findex.rst` — the dependency table row\n   ``` ``flask-appbuilder``   ``==X.Y.Z`` ``` (line ~114).\n4. `providers\u002Ffab\u002FREADME.rst` — the Requirements table row (line ~60). **Do\n   not hand-edit** — it is auto-generated. Regenerate it from the bumped\n   `pyproject.toml` with the `sync-provider-readme` prek hook (Step 8); the hook\n   re-renders the table whenever `pyproject.toml` changes. (Pre-existing bumps\n   that predate this hook left it to release-time regeneration; today the hook is\n   per-commit, so CI flags the drift — run it.)\n5. `uv.lock` — regenerated (see Step 4 for the pinned-uv caveat).\n\n**Conditionally:**\n\n6. `providers\u002Ffab\u002Fsrc\u002Fairflow\u002Fproviders\u002Ffab\u002Fauth_manager\u002Fsecurity_manager\u002Foverride.py`\n   — transplant any relevant upstream FAB changes (new auth hooks, changed\n   signatures, ported fixes). The reference bump added +37 lines here. **Note:**\n   a green alignment test does not prove the transplant is unnecessary — a\n   security fix may live inside a method Airflow vendors (see the 5.2.2 worked\n   example below).\n7. `providers\u002Ffab\u002Fsrc\u002Fairflow\u002Fproviders\u002Ffab\u002Fwww\u002F**` + `providers\u002Ffab\u002Fwww-hash.txt`\n   — only if the new FAB ships changed static assets \u002F templates that are\n   re-vendored (see Step 6).\n8. `providers\u002Ffab\u002Fsrc\u002Fairflow\u002Fproviders\u002Ffab\u002Fmigrations\u002Fversions\u002F**` — only if the\n   new FAB version ships DB migrations (see Step 7).\n\n**Never:** no newsfragment, and do **not** hand-edit `providers\u002Ffab\u002Fdocs\u002Fchangelog.rst`\n— providers are released from `main` and the release manager regenerates the\nchangelog from `git log` (per `providers\u002FAGENTS.md`). The commit subject is the\nchangelog entry.\n\n## Procedure\n\n### Step 1 — Determine the target version and current state\n\n- Read the current pin: `grep flask-appbuilder== providers\u002Ffab\u002Fpyproject.toml`.\n- Resolve the target (PyPI latest, or the user's requested version).\n- Classify the jump: patch \u002F minor \u002F major. For minor\u002Fmajor, skim the FAB\n  release notes (`https:\u002F\u002Fgithub.com\u002Fdpgaspar\u002FFlask-AppBuilder\u002Freleases`) for\n  security-manager \u002F model \u002F template changes before editing.\n\n### Step 2 — Bump the three source pins\n\nEdit in lockstep:\n\n- `providers\u002Ffab\u002Fpyproject.toml` — the `flask-appbuilder==X.Y.Z` line (keep the\n  trailing `# Whenever updating the version, run test_fab_alignment.py to verify.`\n  comment).\n- `test_fab_alignment.py` — `EXPECTED_FAB_VERSION = \"X.Y.Z\"`. It is the tripwire;\n  editing it now is fine — you will keep re-running the test until it and the\n  other three tests pass.\n- `providers\u002Ffab\u002Fdocs\u002Findex.rst` — the dependency-table `==X.Y.Z` row.\n\n### Step 3 — Install the new FAB into the provider venv\n\n`uv --project providers\u002Ffab sync` installs the new pin. Confirm the installed\nversion:\n\n```bash\nuv run --project providers\u002Ffab python -c \\\n  \"import importlib.metadata as m; print(m.version('flask-appbuilder'))\"\n```\n\n### Step 4 — Regenerate uv.lock (pinned uv!)\n\nThe lock **must** be regenerated with the repo's pinned uv version, or it drifts\nhundreds of unrelated marker lines:\n\n```bash\nAIRFLOW_UV_VERSION=$(grep -oE 'AIRFLOW_UV_VERSION=[0-9.]+' Dockerfile.ci | head -1 | cut -d= -f2)\nuvx --from uv==$AIRFLOW_UV_VERSION uv lock\n```\n\nIf a conflict is irrecoverable, delete `uv.lock` and re-run `uv lock` with the\npinned version. Confirm the diff is limited to the FAB bump, not a wholesale\nmarker rewrite.\n\n### Step 5 — Run the alignment test and reconcile override.py\n\n```bash\nuv run --project providers\u002Ffab pytest \\\n  providers\u002Ffab\u002Ftests\u002Funit\u002Ffab\u002Fauth_manager\u002Fsecurity_manager\u002Ftest_fab_alignment.py -xvs\n```\n\nUnder the host sandbox the test needs a writable `AIRFLOW_HOME` and the\nrerun-failures socket disabled — if it errors on a socket `bind` or on\n`~\u002Fairflow`, run it as:\n\n```bash\nAIRFLOW_HOME=\"$TMPDIR\u002Ffab_home\" uv run --project providers\u002Ffab pytest \\\n  providers\u002Ffab\u002Ftests\u002F...\u002Ftest_fab_alignment.py -q -p no:rerunfailures\n```\n\nThe four tests and how to fix each:\n\n1. **`test_fab_version_matches_expected`** — trips on the version mismatch. It\n   passes once `EXPECTED_FAB_VERSION` == installed version, but only after you\n   have done the review below.\n2. **`test_no_unaudited_fab_methods`** — a new FAB public method exists that is\n   neither implemented in `override.py` nor listed in `AUDITED_EXCLUSIONS`.\n   Fix: either implement\u002Foverride it in `override.py`, or add it to\n   `AUDITED_EXCLUSIONS` **with a justification comment**.\n3. **`test_no_stale_exclusions`** — `AUDITED_EXCLUSIONS` lists a method the new\n   FAB no longer has. Fix: remove that entry.\n4. **`test_shared_method_signatures_compatible`** — FAB changed a method\n   signature (new required param). Fix: update the `override.py` signature, or\n   add to `KNOWN_SIGNATURE_DEVIATIONS` if the divergence is intentional.\n\n**The manual review that the test cannot fully automate:** diff the vendored\n`override.py` against the new FAB's `flask_appbuilder\u002Fsecurity\u002Fsqla\u002Fmanager.py`\nand `BaseSecurityManager` and **transplant behavioural changes** (bug fixes, new\nauth event hooks), not just signatures — the test only checks method *presence*\nand *required params*. Locate the installed source:\n\n```bash\nuv run --project providers\u002Ffab python -c \\\n  \"import flask_appbuilder.security.sqla.manager as m; print(m.__file__)\"\n```\n\n### Step 6 — Re-vendor static assets \u002F templates (only if changed)\n\nIf the new FAB changed frontend assets that Airflow vendors under\n`providers\u002Ffab\u002Fsrc\u002Fairflow\u002Fproviders\u002Ffab\u002Fwww\u002F` (templates in\n`templates\u002Fappbuilder\u002F`, static JS\u002FCSS), re-vendor them, then regenerate the\nfingerprint:\n\n```bash\nprek run compile-fab-assets --all-files\n```\n\nThis runs `scripts\u002Fci\u002Fprek\u002Fcompile_provider_assets.py fab` (pnpm build over\n`www\u002F`) and rewrites `providers\u002Ffab\u002Fwww-hash.txt`. A patch bump usually does\n**not** touch assets — skip this step unless FAB's templates\u002Fstatic changed.\nCommit the regenerated `www-hash.txt` if it changed.\n\n### Step 7 — DB migrations (only if FAB ships them)\n\nIf the new FAB adds\u002Fchanges security-model tables, add the corresponding\nmigration under `providers\u002Ffab\u002Fsrc\u002Fairflow\u002Fproviders\u002Ffab\u002Fmigrations\u002Fversions\u002F`\nand run:\n\n```bash\nprek run update-migration-references-fab check-revision-heads-map-fab --all-files\n```\n\nPatch bumps normally have no migrations — skip unless the release notes mention\nschema changes.\n\n### Step 8 — Static checks + tests\n\n```bash\nprek run --from-ref main --stage pre-commit\nuv run --project providers\u002Ffab pytest providers\u002Ffab\u002Ftests\u002Funit\u002Ffab\u002Fauth_manager -xvs\n```\n\nThe pre-commit stage runs `sync-provider-readme` (regenerating `README.rst`) and\nother FAB hooks. Re-run the alignment test until all four tests pass. The full\nprovider suite is `breeze testing providers-tests --test-type \"Providers[fab]\"`.\n\n### Step 9 — Self-review and commit\n\n- `git diff main...HEAD` — verify only the intended files changed, and `uv.lock`\n  is a clean FAB-scoped diff.\n- Commit subject in imperative mood, plain prose, **no** Conventional-Commits\n  prefix, e.g. `Bump flask-appbuilder to X.Y.Z in FAB provider`. Body explains\n  *why* (what upstream changes were mirrored), not what.\n- No newsfragment, no changelog edit (provider release manager regenerates from\n  git log).\n- Prepend your PR to the history list in `providers\u002Ffab\u002FCONTRIBUTING.rst` so the\n  FAB-upgrade record stays current.\n- Push to `origin` and open the PR per the repo's PR conventions.\n\n## Gotchas\n\n- **`EXPECTED_FAB_VERSION` is a second pin.** Forgetting it makes\n  `test_fab_alignment.py` fail even when everything else is correct.\n- **`uv.lock` marker drift.** Always use the pinned uv (Step 4) — a bare\n  `uv lock` rewrites hundreds of environment-marker lines and buries the real diff.\n- **The alignment test uses AST, not import**, to read FAB's `SecurityManager`\n  (to avoid SQLAlchemy model-registry collisions with Airflow's vendored models).\n  A green test proves *structural* alignment; it does **not** prove behavioural\n  parity — Step 5's manual transplant review is still required.\n- **`docs\u002Findex.rst` dependency table** may look auto-generated but the reference\n  PR edited it by hand; if a docs-regen prek hook rewrites it, let the hook win.\n- **`README.rst` is generated, but the sync hook is per-commit.** Don't hand-edit\n  it; run `prek run sync-provider-readme` (or the full pre-commit stage) after\n  bumping `pyproject.toml` — CI fails on the drift otherwise.\n- **Don't touch `providers\u002Ffab\u002Fdocs\u002Fupgrading.rst`** — that is end-user guidance\n  for upgrading the *provider package* in a deployment, not the developer bump\n  workflow.\n\n## Worked example — 5.2.1 to 5.2.2 (patch)\n\nA patch bump that needed **no `override.py` change**, but only after a real\nbehavioural review — the green alignment test alone was not sufficient evidence:\n\n- FAB 5.2.2 shipped three security-manager fixes: LDAP search-filter escaping,\n  OAuth email-allowlist regex anchoring (`email + \"$\"`), and API-login provider\n  validation.\n- All three touch methods Airflow *vendors* (`_search_ldap`, `auth_user_ldap`,\n  `auth_user_oauth`) — so the alignment test passing did **not** mean \"nothing to\n  do\". Each had to be checked by hand:\n  - **LDAP escaping** — Airflow's vendored `_search_ldap` **already** escapes via\n    `ldap.filter.escape_filter_chars` (and adds filter-parenthesis validation); it\n    was ahead of FAB. No transplant.\n  - **OAuth allowlist anchoring** — the match lives in FAB's `AuthOAuthView`\n    (`views.py`), and Airflow's `CustomAuthOAuthView.oauth_authorized` delegates\n    via `super().oauth_authorized()`, so the fix is inherited from the installed\n    FAB 5.2.2. No transplant.\n  - **API-login validation \u002F Azure-JWT warning \u002F uuid4** — in FAB core Airflow\n    doesn't vendor. Inherited.\n- Net change set: `pyproject.toml`, `test_fab_alignment.py`, `docs\u002Findex.rst`,\n  `README.rst` (via hook), `uv.lock`. Commit: `Bump flask-appbuilder to 5.2.2 in\n  FAB provider`.\n\nThe lesson the skill encodes: **for every security\u002Fbehavioural fix in the FAB\nrelease notes, locate the method and check whether Airflow vendors it** — the\nalignment test guards structure, you guard behaviour.\n",{"name":5,"license":15,"description":46},"Upgrade the pinned Flask-AppBuilder (FAB) dependency in the Apache Airflow FAB provider (`providers\u002Ffab\u002F`). Bumps the exact `flask-appbuilder==` pin and its mirror constant, regenerates `uv.lock`, drives the `test_fab_alignment.py` drift tripwire, reviews the vendored security-manager `override.py` against the new upstream FAB, and conditionally re-vendors static assets \u002F DB migrations. Use when asked to \"upgrade FAB\", \"bump flask-appbuilder\", or move the FAB provider to a newer Flask-AppBuilder release.\n","main","f7e28eec7309f5700ca69ec226df41a5c58d5213",".agents\u002Fskills\u002Fupgrade-fab-provider",18346,"https:\u002F\u002Fgithub.com\u002Fapache\u002Fairflow","https:\u002F\u002Fairflow.apache.org\u002F","https:\u002F\u002Fgithub.com\u002Fapache","https:\u002F\u002Fwww.apache.org\u002F","2026-11-12 03:21:46",{"source":8,"skillName":5,"commands":57,"prompt":67,"zipAvailable":68},[58,61,64],{"tool":59,"command":60},"npx","npx skills add apache\u002Fairflow --skill upgrade-fab-provider",{"tool":62,"command":63},"bunx","bunx skills add apache\u002Fairflow --skill upgrade-fab-provider",{"tool":65,"command":66},"pnpm","pnpm dlx skills add apache\u002Fairflow --skill upgrade-fab-provider","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fapache\u002Fairflow\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fupgrade-fab-provider\u002FSKILL.md",true,[70],{"path":71,"resourceType":72,"resourceTypeDesc":73,"sizeBytes":74,"sourceHtmlUrl":75},"scripts\u002Fshannon-find-nvim.sh","SCRIPT","脚本",631,"https:\u002F\u002Fgithub.com\u002Fahmedelgabri\u002Fdotfiles\u002Fblob\u002Fmain\u002Fconfig\u002Fagents\u002Fskills\u002Fshannon\u002Fscripts\u002Fshannon-find-nvim.sh",[77,108,129,157,179,200],{"skillNo":78,"name":79,"nameZh":6,"description":80,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":81,"categories":82,"keywords":93,"publishedAt":41},"01M6ACBAC3S7GHPX3EB91CA2K5","sql-reporting","Conventions and review steps for writing analytics SQL against the warehouse. Use whenever the task involves querying tables, building a report, or aggregating metrics.","https:\u002F\u002Fgithub.com\u002Fapache\u002Fairflow\u002Fblob\u002Fmain\u002Fproviders\u002Fcommon\u002Fai\u002Fsrc\u002Fairflow\u002Fproviders\u002Fcommon\u002Fai\u002Fexample_dags\u002Fskills\u002Fsql-reporting\u002FSKILL.md",[83,88],{"categoryNo":84,"categoryName":85,"taxonomyType":86,"taxonomyTypeDesc":87},"01JBSEEDCAT000000000000010","后端与接口","SCENARIO","使用场景",{"categoryNo":89,"categoryName":90,"taxonomyType":91,"taxonomyTypeDesc":92},"01JBSEEDCAT000000000000033","数据分析师","PROFESSION","适用职业",[94,95,96,97,98,99,100,101,102,103,104,105,106,107],"sql reporting","sql","reporting","writing","review","report","analytics","metrics","tables","warehouse","for","use","task","conventions",{"skillNo":109,"name":110,"nameZh":6,"description":111,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":112,"categories":113,"keywords":114,"publishedAt":41},"01M6ACB7P9C6TV0J1FGWW5HECF","aip-tracker","Track Airflow Improvement Proposal (AIP) implementation progress by comparing Confluence specs against codebase evidence. Use when asked to assess, report on, or compare AIP status.","https:\u002F\u002Fgithub.com\u002Fapache\u002Fairflow\u002Fblob\u002Fmain\u002Fproviders\u002Fcommon\u002Fai\u002Fsrc\u002Fairflow\u002Fproviders\u002Fcommon\u002Fai\u002Fexample_dags\u002Fskills\u002Faip-tracker\u002FSKILL.md",[],[115,116,117,38,99,118,119,24,120,121,122,123,124,105,125,126,127,128],"aip tracker","aip","tracker","status","proposal","track","progress","evidence","improvement","assess","compare","codebase","implementation","confluence",{"skillNo":130,"name":131,"nameZh":6,"description":132,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":133,"categories":134,"keywords":138,"publishedAt":41},"01M6ACB2TYRC6SB5TQ351K2Q7Q","prepare-providers-documentation","Replace the manual commit-by-commit classification step in `breeze release-management prepare-provider-documentation` with AI-driven classification. For each provider with pending changes, analyze every PR (batched into one sub-agent per provider, not one per PR), pay special attention to potentially breaking changes by inspecting the actual diff, scope multi-provider PRs to the current provider's slice, ask the release manager when uncertain, and apply version bumps + changelog entries. Use during the regular provider release cycle as an alternative to the interactive breeze prompts.","https:\u002F\u002Fgithub.com\u002Fapache\u002Fairflow\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fprepare-providers-documentation\u002FSKILL.md",[135],{"categoryNo":136,"categoryName":137,"taxonomyType":86,"taxonomyTypeDesc":87},"01JBSEEDCAT000000000000023","Skill与智能体开发",[139,140,141,142,143,144,145,39,146,147,148,24,149,150,151,152,153,154,155,156],"documentation","prepare providers documentation","prepare","Providers","pr","commit","changelog","scope","cycle","pay","ask","analyze","sub-agent","multi-provider","manager","as","attention","diff",{"skillNo":158,"name":159,"nameZh":6,"description":160,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":161,"categories":162,"keywords":163,"publishedAt":41},"01M6ACAZ1M5SJQH1SHFFY7CXQF","airflow-translations","Add or update translations for the Apache Airflow UI. Guides through setting up locales, scaffolding translation files, translating with locale-specific guidelines, and validating results. Use when working with i18n tasks in airflow-core\u002Fsrc\u002Fairflow\u002Fui\u002Fpublic\u002Fi18n\u002Flocales\u002F.","https:\u002F\u002Fgithub.com\u002Fapache\u002Fairflow\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fairflow-translations\u002FSKILL.md",[],[38,164,165,166,167,9,37,168,169,170,171,172,173,174,175,176,177,24,178,104],"airflow translations","core","guidelines","tasks","update","translation","scaffolding","up","files","guides","i18n","src","ui","add","locale",{"skillNo":180,"name":181,"nameZh":6,"description":182,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":183,"categories":184,"keywords":185,"publishedAt":41},"01M6ACAWMSPCZCQMCJ0WPVTFQ9","airflow-publish-changes","Prepare or carry out authorized publication of Apache Airflow changes, including remote checks, pre-push verification, PR creation, and tracking deferred work. Also use when planning these steps or managing contribution issues.","https:\u002F\u002Fgithub.com\u002Fapache\u002Fairflow\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fairflow-publish-changes\u002FSKILL.md",[],[38,186,187,188,189,143,190,191,192,9,37,141,193,194,24,195,196,197,105,198,199],"airflow publish changes","publish","changes","push","planning","issues","verification","creation","remote","work","pr creation","Publication","contribution","tracking",{"skillNo":201,"name":202,"nameZh":6,"description":203,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":204,"categories":205,"keywords":206,"publishedAt":41},"01M6ACAT3JW1XZD3Z9EPP4H68M","airflow-pr-draft-summary","Draft Apache Airflow commit messages, PR titles and descriptions, or GitHub comments and reviews. Also use for release-note decisions when preparing a contribution.","https:\u002F\u002Fgithub.com\u002Fapache\u002Fairflow\u002Fblob\u002Fmain\u002F.agents\u002Fskills\u002Fairflow-pr-draft-summary\u002FSKILL.md",[],[143,207,38,208,209,210,211,144,9,212,37,39,27,213,24,214,104,105,215,216,198],"pr draft summary","airflow pr draft summary","pr draft","draft","summary","github","messages","commit messages","decisions","comments"]