feat(authz): add source-aware role assignment grants #1112
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Cross-Bundle Test | |
| # Tests every extracted bundle (the lfx-bundles metapackage + each graduated | |
| # lfx-<provider>) against the lfx contract surface it depends on. lfx is the | |
| # PRIMARY axis because the BUNDLE_API contract lives in lfx: a bundle that | |
| # imports cleanly, validates, and passes its tests against a given lfx is | |
| # compatible with that lfx minor. | |
| # | |
| # Cost-control shape (from the bundle-separation epic): | |
| # - contract-smoke (install + import + discover + validate): every bundle, | |
| # on the oldest and latest supported Python. | |
| # - bundle tests (pytest the bundle's own tests/): same matrix. | |
| # - scheduled run: weekly, the exhaustive grid. | |
| # | |
| # The lfx-minor axis is currently a single entry -- the IN-REPO lfx -- because | |
| # the 1.10 line is not yet published to PyPI. When lfx minors publish, add an | |
| # explicit lfx-version matrix dimension here (oldest + latest get the full | |
| # tests; every supported minor gets contract-smoke), and wire langflow RCs as | |
| # the secondary axis via workflow_call from the release pipeline. | |
| on: | |
| workflow_call: | |
| workflow_dispatch: | |
| pull_request: | |
| paths: | |
| - "src/bundles/**" | |
| - "src/lfx/**" | |
| - ".github/workflows/cross-bundle-test.yml" | |
| schedule: | |
| - cron: "0 6 * * 1" # Monday 06:00 UTC -- exhaustive grid | |
| # Stacked bundle PRs would otherwise queue N-bundles x N-pythons jobs per push. | |
| concurrency: | |
| group: cross-bundle-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| jobs: | |
| discover: | |
| name: Discover bundles | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| outputs: | |
| bundles: ${{ steps.find.outputs.bundles }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| persist-credentials: false | |
| - id: find | |
| name: Enumerate src/bundles/*/pyproject.toml | |
| run: | | |
| shopt -s nullglob | |
| dirs=() | |
| for p in src/bundles/*/pyproject.toml; do | |
| dirs+=("$(dirname "$p")") | |
| done | |
| if [ ${#dirs[@]} -eq 0 ]; then | |
| echo "No bundles found under src/bundles/*/" >&2 | |
| echo "bundles=[]" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| json=$(printf '%s\n' "${dirs[@]}" | sort | jq -R . | jq -cs .) | |
| echo "bundles=$json" >> "$GITHUB_OUTPUT" | |
| echo "Discovered bundles: $json" | |
| bundle: | |
| name: ${{ matrix.bundle }} (py${{ matrix.python-version }}) | |
| needs: discover | |
| if: needs.discover.outputs.bundles != '[]' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| bundle: ${{ fromJson(needs.discover.outputs.bundles) }} | |
| # PR runs: oldest + latest supported Python (cost-control smoke). | |
| # Scheduled runs: every supported Python (the exhaustive grid). | |
| # The lfx-minor axis collapses to the in-repo lfx until the 1.10 line | |
| # publishes (see header). | |
| python-version: ${{ github.event_name == 'schedule' && fromJson('["3.10", "3.11", "3.12", "3.13", "3.14"]') || fromJson('["3.10", "3.13"]') }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| persist-credentials: false | |
| - uses: astral-sh/setup-uv@v6 | |
| with: | |
| enable-cache: true | |
| cache-dependency-glob: "uv.lock" | |
| python-version: ${{ matrix.python-version }} | |
| - name: Install in-repo lfx + the bundle into a clean venv | |
| run: | | |
| uv venv | |
| # The in-repo lfx satisfies the bundle's `lfx>=X,<Y` pin and is the | |
| # contract surface under test; pytest is for the bundle's own tests. | |
| # tomli backports stdlib tomllib for the py3.10 matrix leg. | |
| uv pip install ./src/lfx "./${{ matrix.bundle }}" pytest pytest-asyncio tomli | |
| - name: Contract smoke -- import + discovery | |
| run: | | |
| .venv/bin/python - "${{ matrix.bundle }}" <<'PY' | |
| import importlib | |
| import sys | |
| from pathlib import Path | |
| try: | |
| import tomllib # stdlib from 3.11 | |
| except ModuleNotFoundError: | |
| import tomli as tomllib # py3.10 backport, installed above | |
| bundle_dir = Path(sys.argv[1]) | |
| meta = tomllib.loads((bundle_dir / "pyproject.toml").read_text()) | |
| name = meta["project"]["name"] | |
| eps = meta["project"].get("entry-points", {}) | |
| # Manifest bundles declare langflow.extensions; the manifest-less | |
| # metapackage declares lfx.bundles. Either way the declared package | |
| # must import. | |
| modules = list(eps.get("langflow.extensions", {}).values()) or list(eps.get("lfx.bundles", {}).values()) | |
| assert modules, f"{name}: no langflow.extensions or lfx.bundles entry-point declared" | |
| for value in modules: | |
| importlib.import_module(value.split(":", 1)[0]) | |
| print(f" imported {value}") | |
| # The manifest-less metapackage must be discoverable by the loader. | |
| # This venv installs the bundle WITHOUT its per-provider extras, so | |
| # providers whose modules import their SDK at top level degrade with | |
| # `module-import-failed` -- that is the expected graceful-degradation | |
| # contract, not a failure. Structural errors (bundle-empty, | |
| # path-escape, invalid names, ...) still fail the smoke. A provider | |
| # whose every module failed import also reports no components, which | |
| # is fine here; the bundle's own tests cover behavior with SDKs. | |
| if "lfx.bundles" in eps: | |
| from lfx.extension import load_lfx_bundles_extensions | |
| EXPECTED_WITHOUT_EXTRAS = {"module-import-failed"} | |
| results = load_lfx_bundles_extensions() | |
| bad = [e.code for r in results for e in r.errors if e.code not in EXPECTED_WITHOUT_EXTRAS] | |
| assert not bad, f"{name}: lfx.bundles structural discovery errors: {bad}" | |
| providers = sum(1 for r in results if r.bundle) | |
| degraded = sum(1 for r in results for e in r.errors if e.code in EXPECTED_WITHOUT_EXTRAS) | |
| print(f" lfx.bundles discovery OK ({providers} providers; {degraded} module(s) degraded sans extras)") | |
| print(f"{name}: contract smoke OK") | |
| PY | |
| - name: Validate manifest (manifest-shipping bundles only) | |
| run: | | |
| # Manifest bundles ship extension.json; the manifest-less metapackage | |
| # has none and is exempt from `lfx extension validate`. | |
| manifest=$(ls "${{ matrix.bundle }}"/src/*/extension.json 2>/dev/null | head -1 || true) | |
| if [ -n "$manifest" ]; then | |
| .venv/bin/lfx extension validate "$(dirname "$manifest")" | |
| else | |
| echo "manifest-less bundle; skipping extension validate" | |
| fi | |
| - name: Run the bundle's own tests | |
| run: | | |
| if [ -d "${{ matrix.bundle }}/tests" ]; then | |
| # Provider regression tests for the manifest-less metapackage live | |
| # with the provider code and require only that provider's SDKs. | |
| if [ "${{ matrix.bundle }}" = "src/bundles/lfx-bundles" ]; then | |
| uv pip install "./${{ matrix.bundle }}[google]" | |
| fi | |
| .venv/bin/python -m pytest "${{ matrix.bundle }}/tests" -q | |
| else | |
| echo "no tests/ directory in ${{ matrix.bundle }}; skipping" | |
| fi |