Skip to content

feat(models): add approved provider policy store - #14324

Merged
erichare merged 12 commits into
release-1.12.0from
feat/le-1953-approved-provider-policy
Jul 31, 2026
Merged

feat(models): add approved provider policy store#14324
erichare merged 12 commits into
release-1.12.0from
feat/le-1953-approved-provider-policy

Conversation

@erichare

@erichare erichare commented Jul 29, 2026

Copy link
Copy Markdown
Member

Summary

  • add a versioned install-wide approved-provider policy store, migration, and superuser management API with audit coverage
  • enforce canonical provider identity and purpose-aware DISCOVER, CONFIGURE, and USE policy intersections across backend and LFX surfaces
  • synchronize policy across backend and stdio MCP workers with configurable refresh, resilient lifecycle handling, and fail-closed restrictive-state behavior
  • harden flow-builder registry classification so user-authored source or metadata cannot self-exempt or impersonate a built-in provider

Scope

This branch now includes LE-1953 through LE-1956; follow-up PRs #14325, #14326, and #14327 were merged into it.

Test plan

  • uv run ruff check .
  • uv run ruff format --check .
  • focused backend policy API, store, refresh, MCP lifecycle, and model-purpose suites
  • isolated LFX provider-policy, registry, credentials, flow-builder, settings, and component-index suites

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8f7b2443-7a40-463b-ae55-25938f7da9ee

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

The change adds canonical provider-ID resolution, cached synchronous and asynchronous policy evaluation, a versioned singleton allowlist, superuser administration endpoints, startup hydration, and cross-worker refresh handling.

Changes

Model provider policy

Layer / File(s) Summary
Canonical provider identity resolution
src/lfx/src/lfx/base/models/*, src/lfx/src/lfx/base/models/unified_models/*, src/lfx/tests/unit/base/models/*, src/backend/tests/unit/api/v1/test_models_provider_policy.py
Provider selectors resolve to stable IDs across names, aliases, display names, legacy selectors, and opaque fallbacks. LLM and embedding validation reject whitespace-only values.
Cached policy evaluation
src/lfx/src/lfx/services/model_provider_policy/*, src/lfx/tests/unit/services/model_provider_policy/test_policy.py, BUNDLE_API.md, src/lfx/PLUGGABLE_SERVICES.md
Policy resolution supports synchronous and asynchronous hooks, bounded TTL/LRU caching, invalidation, version checks, fail-closed behavior, and legacy subclasses without superclass initialization.
Durable policy state
src/backend/base/langflow/alembic/versions/*, src/backend/base/langflow/services/database/models/*, src/backend/base/langflow/services/model_provider_policy.py, src/backend/base/langflow/services/utils.py, src/backend/tests/unit/alembic/*, src/backend/tests/unit/services/test_model_provider_policy_store.py
A singleton table stores approved provider IDs and versions. Persistence helpers perform atomic replacements, commit updates, hydrate runtime state, and handle refresh failures.
Policy administration API
src/backend/base/langflow/api/v1/model_provider_policy.py, src/backend/base/langflow/api/router.py, src/backend/base/langflow/api/v1/__init__.py, src/backend/tests/unit/api/v1/test_model_provider_policy.py
Superuser-only endpoints read and replace the global policy. Requests validate, deduplicate, and sort provider IDs. Responses include registered-provider metadata.
Policy refresh lifecycle
src/backend/base/langflow/services/task/model_provider_policy_refresh.py, src/backend/base/langflow/main.py, src/backend/base/langflow/agentic/mcp/server.py, src/backend/tests/unit/agentic/mcp/test_server_user_binding.py
The refresh worker starts after service initialization, polls durable versions, applies committed state, fails closed on database errors, and stops during shutdown.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested reviewers: adam-aghili

🚥 Pre-merge checks | ✅ 7 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.03% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Test Quality And Coverage ⚠️ Warning Policy tests are strong, but API success is only tested by direct function calls; GET, POST, and PUT HTTP success/422 cases are missing, as are migration error paths and worker lifecycle tests. Add endpoint-level success and validation/error tests for GET/POST/PUT, migration tests for invalid existing schemas and constraints, and refresh-worker start/stop and failure-recovery coverage.
✅ Passed checks (7 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Test Coverage For New Implementations ✅ Passed The PR adds focused migration, API, store/refresh, and MCP tests plus extensive LFX policy, registry, and runtime tests; backend names follow test_*.py and include SQLite/TestClient coverage.
Test File Naming And Structure ✅ Passed All listed tests use test_*.py under tests/unit; pytest discovery and async auto mode are configured, names are descriptive, and fixtures/finally cleanup cover positive and negative cases.
Excessive Mock Usage Warning ✅ Passed Mocks isolate external lookup, logging, and startup hooks; tests exercise real policy services, FastAPI TestClient routes, and concurrent SQLite persistence.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the main change: adding an approved model-provider policy store.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
⚔️ Resolve merge conflicts 💡
  • Resolve merge conflict in branch feat/le-1953-approved-provider-policy
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/le-1953-approved-provider-policy

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Jul 29, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Migration Validation Passed

All migrations follow the Expand-Contract pattern correctly.

@erichare
erichare force-pushed the feat/le-1953-approved-provider-policy branch from b458aee to 2dbc928 Compare July 29, 2026 23:37
@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Jul 29, 2026
@erichare
erichare force-pushed the feat/le-1953-approved-provider-policy branch from 2dbc928 to e16f76d Compare July 29, 2026 23:46
@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Jul 29, 2026
Base automatically changed from feat/model-provider-governance to release-1.12.0 July 31, 2026 14:57
@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Jul 31, 2026
@erichare
erichare marked this pull request as ready for review July 31, 2026 19:25
@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Jul 31, 2026
@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Jul 31, 2026
* feat(models): enforce provider policy on reads

* feat(models): fail closed on denied provider runtime
@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Jul 31, 2026
* feat(models): enforce provider policy on reads

* feat(models): fail closed on denied provider runtime

* feat(models): govern provider configuration paths
@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Jul 31, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ Test Coverage Advisor

No source changes detected without accompanying tests. Thanks for keeping coverage up! 🎉

Advisory check only — never blocks merge.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/backend/base/langflow/agentic/mcp/server.py (1)

63-75: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Fix the _services_initialized ordering bug and add a regression test for it. _ensure_services() sets _services_initialized = True only after model_provider_policy_refresh_worker.start() succeeds, so a failure in .start() leaves the guard flag False and permits a second, unsafe call to initialize_services(). The existing test does not cover this path.

  • src/backend/base/langflow/agentic/mcp/server.py#L63-L75: move _services_initialized = True to immediately after await initialize_services() succeeds, before starting the refresh worker.
  • src/backend/tests/unit/agentic/mcp/test_server_user_binding.py#L34-L58: after the fix lands, add a test that makes model_provider_policy_refresh_worker.start() raise and asserts _services_initialized is still True and initialize_services is not called a second time on a subsequent _ensure_services() call.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/base/langflow/agentic/mcp/server.py` around lines 63 - 75, Move
the _services_initialized assignment in _ensure_services immediately after
initialize_services succeeds and before
model_provider_policy_refresh_worker.start(), so worker-start failures leave the
guard set. In src/backend/tests/unit/agentic/mcp/test_server_user_binding.py
lines 34-58, add a regression test that makes the worker start raise, verifies
_services_initialized remains True, and confirms a subsequent _ensure_services
call does not invoke initialize_services again.
🧹 Nitpick comments (3)
src/lfx/tests/unit/services/model_provider_policy/test_policy.py (1)

199-205: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Patch the module reference instead of the shared time module.

monkeypatch.setattr(policy_base.time, "monotonic", ...) replaces the attribute on the global time module object, because policy_base.time is that module. Every caller in the process sees a frozen clock until teardown. Bind a stub to the policy_base.time name instead, so the effect stays inside the module under test. The TTL boundary assertions are correct and cover both sides of the >= comparison.

♻️ Proposed change to scope the clock stub
     now = [100.0]
-    monkeypatch.setattr(policy_base.time, "monotonic", lambda: now[0])
+    monkeypatch.setattr(policy_base, "time", SimpleNamespace(monotonic=lambda: now[0]))

Add the import at the top of the file:

from types import SimpleNamespace
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lfx/tests/unit/services/model_provider_policy/test_policy.py` around
lines 199 - 205, Update test_snapshot_cache_expires_after_ttl to replace
policy_base.time with a module-local clock stub rather than mutating the shared
time module; use SimpleNamespace with monotonic bound to the existing now value,
and add the required types.SimpleNamespace import. Preserve the existing TTL
boundary assertions.
src/backend/base/langflow/services/task/model_provider_policy_refresh.py (1)

18-18: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consider a longer default refresh interval.

The default poll interval is 1 second per worker process, forever. Every worker opens a new session and queries the singleton row once per second, even though the policy row changes only when an administrator submits a change. Increase the default interval (for example, to several seconds), or make it configurable, to reduce steady-state database load. The API route already pushes committed state locally at write time (apply_model_provider_policy_state), so the polling worker only needs to catch up other workers, not deliver low-latency updates on its own.

Also applies to: 55-61

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/base/langflow/services/task/model_provider_policy_refresh.py` at
line 18, Increase DEFAULT_MODEL_PROVIDER_POLICY_REFRESH_INTERVAL_SECONDS from
its one-second default to a several-second interval to reduce steady-state
database polling; preserve the existing refresh worker behavior and
configurability if the surrounding implementation already supports it.
src/backend/tests/unit/services/test_model_provider_policy_store.py (1)

71-80: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the missing-singleton-row failure path.

replace_model_provider_policy_state raises RuntimeError when result.rowcount != 1 (see src/backend/base/langflow/services/model_provider_policy.py, lines 72-74). No test in this file exercises that branch. Add a test that calls replace_model_provider_policy_state against a session/table with no singleton row and asserts the RuntimeError is raised.

As per path instructions, "verify the tests actually cover the new or changed behavior rather than acting as placeholders."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/tests/unit/services/test_model_provider_policy_store.py` around
lines 71 - 80, Add a focused test in the policy store test module for
replace_model_provider_policy_state that uses a session/database setup where the
singleton model-provider policy row is absent, then assert the call raises
RuntimeError. Ensure the test executes the real update path and verifies the
missing-row failure rather than mocking or only checking SQL generation.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/backend/base/langflow/agentic/mcp/server.py`:
- Around line 63-75: Move the _services_initialized assignment in
_ensure_services immediately after initialize_services succeeds and before
model_provider_policy_refresh_worker.start(), so worker-start failures leave the
guard set. In src/backend/tests/unit/agentic/mcp/test_server_user_binding.py
lines 34-58, add a regression test that makes the worker start raise, verifies
_services_initialized remains True, and confirms a subsequent _ensure_services
call does not invoke initialize_services again.

---

Nitpick comments:
In `@src/backend/base/langflow/services/task/model_provider_policy_refresh.py`:
- Line 18: Increase DEFAULT_MODEL_PROVIDER_POLICY_REFRESH_INTERVAL_SECONDS from
its one-second default to a several-second interval to reduce steady-state
database polling; preserve the existing refresh worker behavior and
configurability if the surrounding implementation already supports it.

In `@src/backend/tests/unit/services/test_model_provider_policy_store.py`:
- Around line 71-80: Add a focused test in the policy store test module for
replace_model_provider_policy_state that uses a session/database setup where the
singleton model-provider policy row is absent, then assert the call raises
RuntimeError. Ensure the test executes the real update path and verifies the
missing-row failure rather than mocking or only checking SQL generation.

In `@src/lfx/tests/unit/services/model_provider_policy/test_policy.py`:
- Around line 199-205: Update test_snapshot_cache_expires_after_ttl to replace
policy_base.time with a module-local clock stub rather than mutating the shared
time module; use SimpleNamespace with monotonic bound to the existing now value,
and add the required types.SimpleNamespace import. Preserve the existing TTL
boundary assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2a4fa4c5-e557-4e05-9cde-a383b4518a63

📥 Commits

Reviewing files that changed from the base of the PR and between 2da52d0 and e16f76d.

📒 Files selected for processing (30)
  • BUNDLE_API.md
  • src/backend/base/langflow/agentic/mcp/server.py
  • src/backend/base/langflow/alembic/versions/e8f1a2b3c4d5_add_model_provider_policy.py
  • src/backend/base/langflow/api/router.py
  • src/backend/base/langflow/api/v1/__init__.py
  • src/backend/base/langflow/api/v1/model_provider_policy.py
  • src/backend/base/langflow/main.py
  • src/backend/base/langflow/services/database/models/__init__.py
  • src/backend/base/langflow/services/database/models/model_provider_policy/__init__.py
  • src/backend/base/langflow/services/database/models/model_provider_policy/model.py
  • src/backend/base/langflow/services/model_provider_policy.py
  • src/backend/base/langflow/services/task/model_provider_policy_refresh.py
  • src/backend/base/langflow/services/utils.py
  • src/backend/tests/unit/agentic/mcp/test_server_user_binding.py
  • src/backend/tests/unit/alembic/test_model_provider_policy_migration.py
  • src/backend/tests/unit/api/v1/test_model_provider_policy.py
  • src/backend/tests/unit/api/v1/test_models_provider_policy.py
  • src/backend/tests/unit/services/test_model_provider_policy_store.py
  • src/lfx/PLUGGABLE_SERVICES.md
  • src/lfx/src/lfx/base/models/provider_registry.py
  • src/lfx/src/lfx/base/models/unified_models/instantiation.py
  • src/lfx/src/lfx/base/models/unified_models/provider_queries.py
  • src/lfx/src/lfx/services/model_provider_policy/__init__.py
  • src/lfx/src/lfx/services/model_provider_policy/base.py
  • src/lfx/src/lfx/services/model_provider_policy/service.py
  • src/lfx/src/lfx/services/model_provider_policy/utils.py
  • src/lfx/tests/unit/base/models/test_build_config_sticky_default.py
  • src/lfx/tests/unit/base/models/test_get_llm_error_messages.py
  • src/lfx/tests/unit/base/models/test_provider_registry.py
  • src/lfx/tests/unit/services/model_provider_policy/test_policy.py

@codecov

codecov Bot commented Jul 31, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.60870% with 54 lines in your changes missing coverage. Please review.
✅ Project coverage is 55.04%. Comparing base (0da3204) to head (cc06a09).
⚠️ Report is 5 commits behind head on release-1.12.0.

Files with missing lines Patch % Lines
...rc/lfx/base/models/unified_models/instantiation.py 34.61% 13 Missing and 4 partials ⚠️
...lfx/src/lfx/mcp/flow_builder_tools/mutate_tools.py 92.66% 4 Missing and 4 partials ⚠️
.../src/lfx/base/models/unified_models/credentials.py 66.66% 3 Missing and 3 partials ⚠️
...rc/lfx/src/lfx/mcp/flow_builder_tools/run_tools.py 71.42% 6 Missing ⚠️
...low/services/task/model_provider_policy_refresh.py 92.75% 5 Missing ⚠️
.../src/lfx/services/model_provider_policy/service.py 90.90% 2 Missing and 2 partials ⚠️
...kend/base/langflow/api/v1/model_provider_policy.py 96.66% 2 Missing ⚠️
src/backend/base/langflow/api/v1/models.py 94.59% 2 Missing ⚠️
src/backend/base/langflow/main.py 71.42% 2 Missing ⚠️
src/backend/base/langflow/agentic/mcp/server.py 96.15% 1 Missing ⚠️
... and 1 more
Additional details and impacted files

Impacted file tree graph

@@                Coverage Diff                 @@
##           release-1.12.0   #14324      +/-   ##
==================================================
- Coverage           62.18%   55.04%   -7.15%     
==================================================
  Files                2343     2328      -15     
  Lines              236524   230978    -5546     
  Branches            35151    18185   -16966     
==================================================
- Hits               147088   127132   -19956     
- Misses              87616   102004   +14388     
- Partials             1820     1842      +22     
Flag Coverage Δ
backend 69.83% <95.93%> (+0.42%) ⬆️
frontend 49.04% <ø> (-11.58%) ⬇️
lfx 60.12% <83.92%> (-0.37%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...e/langflow/agentic/flows/flow_builder_assistant.py 100.00% <ø> (ø)
...ase/langflow/agentic/services/agent_run_context.py 100.00% <100.00%> (ø)
...ase/langflow/agentic/services/assistant_service.py 87.95% <100.00%> (+0.65%) ⬆️
...base/langflow/agentic/services/provider_service.py 98.24% <100.00%> (+0.03%) ⬆️
src/backend/base/langflow/api/router.py 100.00% <100.00%> (ø)
src/backend/base/langflow/api/v1/model_options.py 100.00% <100.00%> (+10.00%) ⬆️
...ces/database/models/model_provider_policy/model.py 100.00% <100.00%> (ø)
src/backend/base/langflow/services/utils.py 86.85% <100.00%> (+0.36%) ⬆️
src/lfx/src/lfx/base/models/provider_registry.py 78.13% <100.00%> (+3.18%) ⬆️
...rc/lfx/base/models/unified_models/model_catalog.py 81.18% <100.00%> (ø)
... and 16 more

... and 964 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Jul 31, 2026
@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Frontend Unit Test Coverage Report

Coverage Summary

Lines Statements Branches Functions
Coverage: 49%
49.04% (70691/144144) 70.11% (9860/14063) 46.64% (1627/3488)

Unit Test Results

Tests Skipped Failures Errors Time
5466 0 💤 0 ❌ 0 🔥 19m 15s ⏱️

@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Jul 31, 2026
@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Jul 31, 2026
@github-actions github-actions Bot added enhancement New feature or request and removed enhancement New feature or request labels Jul 31, 2026
@erichare
erichare merged commit 7ae049d into release-1.12.0 Jul 31, 2026
101 checks passed
@erichare
erichare deleted the feat/le-1953-approved-provider-policy branch July 31, 2026 20:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant