refactor(plugins): migrate in-tree plugins to PyPI packages (cpex-*)#3965
refactor(plugins): migrate in-tree plugins to PyPI packages (cpex-*)#3965brian-hussey merged 14 commits intomainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR migrates the gateway’s rate limiter plugin from an in-tree Python implementation to the external cpex-rate-limiter PyPI package, updating configs/build artifacts accordingly and removing the old plugin code and its tests.
Changes:
- Add
cpex-rate-limiter>=0.0.2as a new optional dependency group ([plugins]) and lock it inuv.lock. - Update plugin configuration files to reference
cpex_rate_limiter.RateLimiterPlugin(and disable it in default/unit-test configs). - Install the new optional dependency group in
Containerfile.liteand remove the in-treeplugins/rate_limiterimplementation and related tests/docs.
Reviewed changes
Copilot reviewed 16 out of 17 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
uv.lock |
Locks cpex-rate-limiter and adds the plugins extra. |
pyproject.toml |
Introduces optional dependency group plugins with cpex-rate-limiter. |
Containerfile.lite |
Installs .[...,plugins] so container builds include the package. |
plugins/config.yaml |
Switches rate limiter kind to cpex_rate_limiter... and disables by default. |
plugins/config-pii-guardian-policy.yaml |
Switches kind to external rate limiter. |
tests/unit/.../init_hooks_plugins_test.yaml |
Switches kind to external rate limiter and disables it for unit tests. |
tests/performance/plugins/config.yaml |
Switches kind to external rate limiter (still enabled for perf runs). |
plugins/webhook_notification/test_config.yaml |
Switches kind to external rate limiter for webhook testing config. |
docs/docs/using/plugins/plugins.md |
Updates rate limiter entry to point to external package. |
llms/plugins-llms.md |
Updates plugin list entry to cpex-rate-limiter. |
docs/docs/testing/unittest.md |
Removes rate limiter unit test from the unit test list. |
plugins/rate_limiter/* |
Removes in-tree plugin implementation, manifest, and README. |
tests/unit/.../rate_limiter/test_rate_limiter.py |
Removes in-tree rate limiter unit tests. |
tests/integration/test_rate_limiter.py |
Removes in-tree rate limiter integration tests. |
Comments suppressed due to low confidence (1)
tests/performance/plugins/config.yaml:248
- This performance config enables
cpex_rate_limiter.RateLimiterPlugin(mode: permissive). Sincecpex-rate-limiteris an optional extra, runningtests/performance/test_plugins_performance.pywill fail unless users have installed.[plugins]. Consider either disabling the rate limiter here by default or adding an explicit note in this config about the required extra/install command.
# Rate limiter (fixed window, in-memory)
- name: RateLimiterPlugin
kind: cpex_rate_limiter.RateLimiterPlugin
description: Per-user/tenant/tool rate limits
version: 0.1.0
author: Mihai Criveti
hooks: [prompt_pre_fetch, tool_pre_invoke]
tags: [limits, throttle]
mode: permissive
priority: 20
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| kind: plugins.rate_limiter.rate_limiter.RateLimiterPlugin | ||
| kind: cpex_rate_limiter.RateLimiterPlugin | ||
| description: Per-user/tenant/tool rate limits | ||
| version: 0.1.0 |
There was a problem hiding this comment.
The plugin version: 0.1.0 no longer matches the implementation referenced by kind: cpex_rate_limiter.RateLimiterPlugin (package is pinned to >=0.0.2). Since plugin version is surfaced via admin plugin APIs/UI, consider updating this value to the external package version (or omitting it like plugins/config.yaml does) to avoid misleading metadata.
| version: 0.1.0 |
| kind: "plugins.rate_limiter.rate_limiter.RateLimiterPlugin" | ||
| kind: "cpex_rate_limiter.RateLimiterPlugin" | ||
| description: "Per-user/tenant/tool rate limits" | ||
| version: "0.1.0" |
There was a problem hiding this comment.
The plugin version: "0.1.0" is now inconsistent with kind: "cpex_rate_limiter.RateLimiterPlugin" (external package is >=0.0.2). Because plugin version is exposed in admin plugin APIs/UI, update this value to the external package version or remove it to avoid misleading operators.
| version: "0.1.0" |
| kind: "plugins.rate_limiter.rate_limiter.RateLimiterPlugin" | ||
| kind: "cpex_rate_limiter.RateLimiterPlugin" | ||
| description: "Per-user/tenant/tool rate limits" | ||
| version: "0.1.0" |
There was a problem hiding this comment.
This fixture still advertises version: "0.1.0" while kind now points to the external cpex_rate_limiter.RateLimiterPlugin (>=0.0.2). Even though the plugin is disabled in this test config, keeping the version accurate (or omitting it) helps avoid confusion when debugging plugin metadata or admin plugin listings.
| version: "0.1.0" |
| # Rate Limiter Plugin (will generate rate limit violations) | ||
| - name: "RateLimiter" | ||
| kind: "plugins.rate_limiter.rate_limiter.RateLimiterPlugin" | ||
| kind: "cpex_rate_limiter.RateLimiterPlugin" | ||
| hooks: ["tool_pre_invoke"] | ||
| mode: "permissive" # Don't block for testing | ||
| priority: 200 |
There was a problem hiding this comment.
This dev test config now depends on the external cpex-rate-limiter package (kind: cpex_rate_limiter.RateLimiterPlugin). Add an explicit note here about installing the optional extra (e.g. uv pip install '.[plugins]') or set this plugin to mode: disabled by default, otherwise following the config file may fail with an import error in environments without that extra installed.
a0061cb to
bed1dc6
Compare
bed1dc6 to
f101a18
Compare
|
The import works fine on my side and I can see the plugin. the author field, I think @gandhipratik203 is the author for the Rust version we’re uploading, although we might want to keep it as “Contextforge developers” for consistency. One last thing: should we consider keeping the Python plugin as an example somewhere instead of removing it entirely? |
|
Hi @jonpspri @crivetimihai — thanks for putting this together. This is a good move — distributing plugins as pre-compiled PyPI packages is a well-established pattern (ruff, orjson, pydantic-core all do the same thing) and makes Rust acceleration accessible without requiring a toolchain on the gateway. I'd like to help this move forward:
|
6831640 to
1ee8234
Compare
Signed-off-by: lucarlig <luca.carlig@ibm.com>
|
Agreed with @gandhipratik203 on the plan: Step 1 Step 2 For the old Python plugins, @jonpspri will spin up an example repo. I’ll also add an ADR to document this. Reasoning: @brian.hussey @jonpspri — does this approach work? |
Signed-off-by: lucarlig <luca.carlig@ibm.com>
171c80e to
e3ab892
Compare
e7163a0 to
751be5b
Compare
|
@gandhipratik203 Short follow-up: I kept and updated the integration coverage for the packaged cpex_* plugins, fixed the shipped config/import drift, added gateway-side smoke coverage for the packaged plugin wiring, aligned the external package version metadata, and added ADR-048 to document the extraction order and repo split rationale. I also removed the gateway runtime dependency on cpex_retry_with_backoff internals and added targeted tests for the new retry parsing paths. Local full pytest, diff-cover, and doctests all passed after the follow-up fixes. |
gandhipratik203
left a comment
There was a problem hiding this comment.
Thanks for turning these around so quickly — really appreciate the responsiveness, and the scope decisions feel right.
- ✅ Integration tests preserved (
test_rate_limiter.pyandtest_encoded_exfil.pykept in-tree) — good call keeping them where they're already wired into CI. - ✅
url_reputationpulled from the migration scope — appreciate the conservative approach here, much better to ship it when it's ready. - ✅ ADR added (
048-extract-rust-backed-plugins-first-and-preserve-python-examples.md) — nice to have the rationale documented for future contributors. - ✅ Luca's plan to freeze plugin code in cpex-plugins and decouple CI is a clean way to stabilize things and addresses the testing concern well.
Approving — thanks again @lucarlig @jonpspri for the quick turnaround on this.
…r package Remove the in-tree rate_limiter plugin and replace it with the cpex-rate-limiter PyPI package, a compiled Rust extension providing the same RateLimiterPlugin class with additional algorithms (sliding-window, token-bucket) alongside the original fixed-window. - Add cpex-rate-limiter>=0.0.2 as a [plugins] optional dependency - Update Containerfile.lite to install the plugins extra - Remove plugins/rate_limiter/ source directory - Remove unit and integration tests that imported plugin internals - Update all config files to use cpex_rate_limiter.RateLimiterPlugin - Disable RateLimiterPlugin in test fixture config (package not available in unit test environment) - Update documentation to reflect the external package Signed-off-by: Jonathan Springer <jps@s390x.com> Signed-off-by: lucarlig <luca.carlig@ibm.com>
…ngine, benchmarks, and validation (#3809) * feat(rate-limiter): pluggable algorithms, tenant isolation fix, and scale load test - Add pluggable algorithm strategy: fixed_window, sliding_window, token_bucket - Add Redis backend for shared cross-instance rate limiting - Fix tenant isolation: skip by_tenant when tenant_id is None - Fix sliding window: sweep expired timestamps before counting - Fix backend validation: restore _validate_config check - Fix token bucket memory path: apply max(1,...) guard to reset timestamp - Add Redis integration tests for all three algorithms - Add direct regression tests for get_current_user tenant_id fallback - Add scale load test with Redis memory timeline and live algorithm detection - Add RL_PACE_MULTIPLIER for near-limit pace testing and boundary burst detection - Remove redundant algorithm locustfile; scale file is canonical - Correct stale comments and README limitations Signed-off-by: Pratik Gandhi <gandhipratik203@gmail.com> * feat(rate-limiter): add Rust-backed engine, check() API, benchmarks, and validation - Rust-backed sliding window engine with pyo3-log integration - check() API with tenant propagation, sweep/retry-after support - Eliminate redundant ZRANGE in sliding window Lua script - Fix detect-secrets baseline for rate limiter load tests - Clarify memory backend is single-instance only in docs Signed-off-by: Pratik Gandhi <gandhipratik203@gmail.com> * chore: regenerate detect-secrets baseline after rebase Signed-off-by: Pratik Gandhi <gandhipratik203@gmail.com> * refactor(rate-limiter): review fixes, Redis hardening, key-format parity tests - Extract _dispatch_hook() shared by prompt_pre_fetch and tool_pre_invoke, reducing each hook to a single-line wrapper - Elevate Redis val_i64/val_f64 parse-error logging from warn to error so silent fail-open degradation surfaces in operator dashboards - Clamp sliding-window reset_timestamp with .max(1) so it is always strictly in the future even when the oldest entry expires in < 1 s - Add 5 s tokio::time::timeout around Redis connection establishment to prevent indefinite blocking on network partition - Replace silent except-pass in EVALSHA SHA tracking with logger.debug - Document dual Lua-script invariant (rolling-upgrade key-format parity) in both Python RedisBackend docstring and Rust redis_backend.rs header - Add 7 parametrized test_redis_key_format_parity_* tests validating that Python and Rust produce identical Redis keys for the same inputs - Revert unrelated .pyi stub changes for encoded_exfil_detection, pii_filter, retry_with_backoff, and secrets_detection Signed-off-by: Jonathan Springer <jps@s390x.com> * fix: strip trailing whitespace in pyi stubs, remove accidental .claude/ralph-loop.local.md - Remove plugins_rust/rate_limiter/.claude/ralph-loop.local.md which was accidentally committed — this is a local Claude Code loop state file and should never have been checked in. - Fix trailing whitespace in plugins_rust/rate_limiter/python/ rate_limiter_rust/__init__.pyi docstrings to pass pre-commit hooks. Signed-off-by: Pratik Gandhi <gandhipratik203@gmail.com> * chore: regenerate detect-secrets baseline for new exfil test strings Update .secrets.baseline after adding test_extra_sensitive_keywords in plugins_rust/encoded_exfil_detection/src/lib.rs:969 which contains a fake credential string that triggers the Secret Keyword detector. All new entries are false positives (test data). Signed-off-by: Pratik Gandhi <gandhipratik203@gmail.com> * chore: audit new detect-secrets baseline entries as false positives The baseline regeneration reset is_secret to null for entries whose line numbers shifted. Mark all 17 unaudited entries as is_secret=false (test data, example configs, fake credentials) to pass the --fail-on-unaudited pre-commit check. Signed-off-by: Pratik Gandhi <gandhipratik203@gmail.com> --------- Signed-off-by: Pratik Gandhi <gandhipratik203@gmail.com> Signed-off-by: Jonathan Springer <jps@s390x.com> Co-authored-by: Jonathan Springer <jps@s390x.com> Signed-off-by: lucarlig <luca.carlig@ibm.com>
…ation (#3839) Implement automatic tool discovery for upstream MCP servers via usage-aware adaptive polling. The gateway can now continuously synchronise tool lists from registered servers without manual intervention. Server classification (hot/cold): - Classify servers based on MCP session pool usage patterns - Hot servers (top 20% by recent usage): polled at 1x base interval - Cold servers (remaining 80%): polled at 3x base interval - Classification is deterministic: sorted by recency, active sessions, use count, and URL for tie-breaking - Leader election via Redis with TTL renewal for multi-worker coordination - Falls back to local-only operation without Redis Integration with GatewayService: - Health checks respect hot/cold classification intervals - Auto-refresh of tools/resources/prompts respects classification - Fail-open on classification errors (poll anyway) - Poll timestamps tracked via Redis with TTL expiry - Uses base gateway URL (pre-auth) for classification lookups to avoid leaking query-param auth secrets to Redis Configuration: - AUTO_REFRESH_SERVERS=true enables automatic tool sync (default: false) - GATEWAY_AUTO_REFRESH_INTERVAL=300 sets base polling interval - HOT_COLD_CLASSIFICATION_ENABLED=false (opt-in, requires Redis) Includes comprehensive tests with 100% coverage on the new ServerClassificationService and integration tests for the GatewayService hot/cold polling paths. Closes #3734 Signed-off-by: Lang-Akshay <akshay.shinde26@ibm.com> Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> Signed-off-by: lucarlig <luca.carlig@ibm.com>
…r package Remove the in-tree rate_limiter plugin and replace it with the cpex-rate-limiter PyPI package, a compiled Rust extension providing the same RateLimiterPlugin class with additional algorithms (sliding-window, token-bucket) alongside the original fixed-window. - Add cpex-rate-limiter>=0.0.2 as a [plugins] optional dependency - Update Containerfile.lite to install the plugins extra - Remove plugins/rate_limiter/ source directory - Remove unit and integration tests that imported plugin internals - Update all config files to use cpex_rate_limiter.RateLimiterPlugin - Disable RateLimiterPlugin in test fixture config (package not available in unit test environment) - Update documentation to reflect the external package Signed-off-by: Jonathan Springer <jps@s390x.com> Signed-off-by: lucarlig <luca.carlig@ibm.com>
Remove all plugins_rust/ build infrastructure and update references across Containerfiles, Makefile, CI workflows, pre-commit configs, CODEOWNERS, and documentation to reflect that plugins are now distributed as PyPI packages (cpex-*) via the [plugins] optional extra. - Remove Rust plugin builder stages from all Containerfiles - Remove ~100 lines of rust-* plugin Makefile targets (keep mcp-runtime) - Add --extra plugins to CI pytest workflow - Add [plugins] extra to install-dev Makefile target - Update tool_service.py import to use cpex_retry_with_backoff - Update plugin kind paths in 7 doc files to cpex_pii_filter.* - Clean up pre-commit, CODEOWNERS, MANIFEST.in, whitesource, .gitignore Signed-off-by: Jonathan Springer <jps@s390x.com> Signed-off-by: lucarlig <luca.carlig@ibm.com>
Round 1 (blockers + high): - Restore exclude-newer = "10 days" in pyproject.toml; replace stale langchain/requests pins with cpex-* per-package overrides anchored to 2026-04-09 so the plugins resolve newer than the global window - Guard cpex_retry_with_backoff import in tool_service.py with try/except ImportError; falls back to (None, True) for the Python pipeline when the optional [plugins] extra is not installed - Delete orphaned .github/workflows/rust-plugins.yml and the associated test cases in tests/unit/test_rust_plugins_workflow.py; drop the workflow card from docs/docs/architecture/explorer.html - Delete orphaned docs/docs/using/plugins/rust-plugins.md and remove it from docs/docs/using/plugins/.pages mkdocs nav - Harden docker-entrypoint.sh install_plugin_requirements: canonicalize /app and the resolved requirements path with readlink -f and require the path to live under /app/, log non-comment lines from the requirements file before pip runs, and skip cleanly on validation failure - Delete PLUGIN-MIGRATION-PLAN.md (one-time planning doc) - Add COPY plugins/requirements.txt to Containerfile.scratch (the layered Containerfile.lite already had it; the broad COPY . in Containerfile already includes it) Round 2 (medium + low): - Bump cpex-* version pin floors in pyproject.toml [plugins] to match resolved versions in uv.lock (cpex-rate-limiter>=0.0.3, cpex-encoded-exfil-detection>=0.2.0, cpex-pii-filter>=0.2.0, cpex-url-reputation>=0.1.1) - Add Prerequisites section to tests/performance/PLUGIN_PROFILING.md documenting the [plugins] extra requirement - Add Status: Partially superseded note to ADR-041 explaining that plugins_rust/ was removed when in-tree Rust plugins migrated to PyPI packages - Document upgrade semantics in plugins/requirements.txt header (pip without --upgrade skips already-satisfied constraints) - Add importlib.util.find_spec() precheck to tests/performance/test_plugins_performance.py main(); the script now skips cleanly with an actionable message if any of the five cpex packages referenced by the perf config are missing - Rename tests/unit/test_rust_plugins_workflow.py to test_go_toolchain_pinning.py to match its remaining contents (Go workflow pin and Makefile toolchain assertion) Follow-ups tracked in #4116 and IBM/cpex-plugins#21 for the longer-term tool_service.py refactor that will eliminate the cross-package import entirely. Signed-off-by: Jonathan Springer <jps@s390x.com> Signed-off-by: lucarlig <luca.carlig@ibm.com>
Signed-off-by: lucarlig <luca.carlig@ibm.com>
Signed-off-by: lucarlig <luca.carlig@ibm.com>
Signed-off-by: lucarlig <luca.carlig@ibm.com>
Signed-off-by: lucarlig <luca.carlig@ibm.com>
Signed-off-by: lucarlig <luca.carlig@ibm.com>
Signed-off-by: lucarlig <luca.carlig@ibm.com>
Signed-off-by: lucarlig <luca.carlig@ibm.com>
Signed-off-by: lucarlig <luca.carlig@ibm.com>
e63b14f to
fc5a959
Compare
brian-hussey
left a comment
There was a problem hiding this comment.
Approval as @lucarlig and @gandhipratik203 have previously approved.
…3965) * refactor(plugins): replace in-tree rate_limiter with cpex-rate-limiter package Remove the in-tree rate_limiter plugin and replace it with the cpex-rate-limiter PyPI package, a compiled Rust extension providing the same RateLimiterPlugin class with additional algorithms (sliding-window, token-bucket) alongside the original fixed-window. - Add cpex-rate-limiter>=0.0.2 as a [plugins] optional dependency - Update Containerfile.lite to install the plugins extra - Remove plugins/rate_limiter/ source directory - Remove unit and integration tests that imported plugin internals - Update all config files to use cpex_rate_limiter.RateLimiterPlugin - Disable RateLimiterPlugin in test fixture config (package not available in unit test environment) - Update documentation to reflect the external package Signed-off-by: Jonathan Springer <jps@s390x.com> Signed-off-by: lucarlig <luca.carlig@ibm.com> * feat(rate-limiter): pluggable algorithms with Rust-backed execution engine, benchmarks, and validation (#3809) * feat(rate-limiter): pluggable algorithms, tenant isolation fix, and scale load test - Add pluggable algorithm strategy: fixed_window, sliding_window, token_bucket - Add Redis backend for shared cross-instance rate limiting - Fix tenant isolation: skip by_tenant when tenant_id is None - Fix sliding window: sweep expired timestamps before counting - Fix backend validation: restore _validate_config check - Fix token bucket memory path: apply max(1,...) guard to reset timestamp - Add Redis integration tests for all three algorithms - Add direct regression tests for get_current_user tenant_id fallback - Add scale load test with Redis memory timeline and live algorithm detection - Add RL_PACE_MULTIPLIER for near-limit pace testing and boundary burst detection - Remove redundant algorithm locustfile; scale file is canonical - Correct stale comments and README limitations Signed-off-by: Pratik Gandhi <gandhipratik203@gmail.com> * feat(rate-limiter): add Rust-backed engine, check() API, benchmarks, and validation - Rust-backed sliding window engine with pyo3-log integration - check() API with tenant propagation, sweep/retry-after support - Eliminate redundant ZRANGE in sliding window Lua script - Fix detect-secrets baseline for rate limiter load tests - Clarify memory backend is single-instance only in docs Signed-off-by: Pratik Gandhi <gandhipratik203@gmail.com> * chore: regenerate detect-secrets baseline after rebase Signed-off-by: Pratik Gandhi <gandhipratik203@gmail.com> * refactor(rate-limiter): review fixes, Redis hardening, key-format parity tests - Extract _dispatch_hook() shared by prompt_pre_fetch and tool_pre_invoke, reducing each hook to a single-line wrapper - Elevate Redis val_i64/val_f64 parse-error logging from warn to error so silent fail-open degradation surfaces in operator dashboards - Clamp sliding-window reset_timestamp with .max(1) so it is always strictly in the future even when the oldest entry expires in < 1 s - Add 5 s tokio::time::timeout around Redis connection establishment to prevent indefinite blocking on network partition - Replace silent except-pass in EVALSHA SHA tracking with logger.debug - Document dual Lua-script invariant (rolling-upgrade key-format parity) in both Python RedisBackend docstring and Rust redis_backend.rs header - Add 7 parametrized test_redis_key_format_parity_* tests validating that Python and Rust produce identical Redis keys for the same inputs - Revert unrelated .pyi stub changes for encoded_exfil_detection, pii_filter, retry_with_backoff, and secrets_detection Signed-off-by: Jonathan Springer <jps@s390x.com> * fix: strip trailing whitespace in pyi stubs, remove accidental .claude/ralph-loop.local.md - Remove plugins_rust/rate_limiter/.claude/ralph-loop.local.md which was accidentally committed — this is a local Claude Code loop state file and should never have been checked in. - Fix trailing whitespace in plugins_rust/rate_limiter/python/ rate_limiter_rust/__init__.pyi docstrings to pass pre-commit hooks. Signed-off-by: Pratik Gandhi <gandhipratik203@gmail.com> * chore: regenerate detect-secrets baseline for new exfil test strings Update .secrets.baseline after adding test_extra_sensitive_keywords in plugins_rust/encoded_exfil_detection/src/lib.rs:969 which contains a fake credential string that triggers the Secret Keyword detector. All new entries are false positives (test data). Signed-off-by: Pratik Gandhi <gandhipratik203@gmail.com> * chore: audit new detect-secrets baseline entries as false positives The baseline regeneration reset is_secret to null for entries whose line numbers shifted. Mark all 17 unaudited entries as is_secret=false (test data, example configs, fake credentials) to pass the --fail-on-unaudited pre-commit check. Signed-off-by: Pratik Gandhi <gandhipratik203@gmail.com> --------- Signed-off-by: Pratik Gandhi <gandhipratik203@gmail.com> Signed-off-by: Jonathan Springer <jps@s390x.com> Co-authored-by: Jonathan Springer <jps@s390x.com> Signed-off-by: lucarlig <luca.carlig@ibm.com> * feat(discovery): add automatic tool discovery with hot/cold classification (#3839) Implement automatic tool discovery for upstream MCP servers via usage-aware adaptive polling. The gateway can now continuously synchronise tool lists from registered servers without manual intervention. Server classification (hot/cold): - Classify servers based on MCP session pool usage patterns - Hot servers (top 20% by recent usage): polled at 1x base interval - Cold servers (remaining 80%): polled at 3x base interval - Classification is deterministic: sorted by recency, active sessions, use count, and URL for tie-breaking - Leader election via Redis with TTL renewal for multi-worker coordination - Falls back to local-only operation without Redis Integration with GatewayService: - Health checks respect hot/cold classification intervals - Auto-refresh of tools/resources/prompts respects classification - Fail-open on classification errors (poll anyway) - Poll timestamps tracked via Redis with TTL expiry - Uses base gateway URL (pre-auth) for classification lookups to avoid leaking query-param auth secrets to Redis Configuration: - AUTO_REFRESH_SERVERS=true enables automatic tool sync (default: false) - GATEWAY_AUTO_REFRESH_INTERVAL=300 sets base polling interval - HOT_COLD_CLASSIFICATION_ENABLED=false (opt-in, requires Redis) Includes comprehensive tests with 100% coverage on the new ServerClassificationService and integration tests for the GatewayService hot/cold polling paths. Closes #3734 Signed-off-by: Lang-Akshay <akshay.shinde26@ibm.com> Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> Signed-off-by: lucarlig <luca.carlig@ibm.com> * refactor(plugins): replace in-tree rate_limiter with cpex-rate-limiter package Remove the in-tree rate_limiter plugin and replace it with the cpex-rate-limiter PyPI package, a compiled Rust extension providing the same RateLimiterPlugin class with additional algorithms (sliding-window, token-bucket) alongside the original fixed-window. - Add cpex-rate-limiter>=0.0.2 as a [plugins] optional dependency - Update Containerfile.lite to install the plugins extra - Remove plugins/rate_limiter/ source directory - Remove unit and integration tests that imported plugin internals - Update all config files to use cpex_rate_limiter.RateLimiterPlugin - Disable RateLimiterPlugin in test fixture config (package not available in unit test environment) - Update documentation to reflect the external package Signed-off-by: Jonathan Springer <jps@s390x.com> Signed-off-by: lucarlig <luca.carlig@ibm.com> * refactor(plugins): update build, CI, and docs for PyPI plugin migration Remove all plugins_rust/ build infrastructure and update references across Containerfiles, Makefile, CI workflows, pre-commit configs, CODEOWNERS, and documentation to reflect that plugins are now distributed as PyPI packages (cpex-*) via the [plugins] optional extra. - Remove Rust plugin builder stages from all Containerfiles - Remove ~100 lines of rust-* plugin Makefile targets (keep mcp-runtime) - Add --extra plugins to CI pytest workflow - Add [plugins] extra to install-dev Makefile target - Update tool_service.py import to use cpex_retry_with_backoff - Update plugin kind paths in 7 doc files to cpex_pii_filter.* - Clean up pre-commit, CODEOWNERS, MANIFEST.in, whitesource, .gitignore Signed-off-by: Jonathan Springer <jps@s390x.com> Signed-off-by: lucarlig <luca.carlig@ibm.com> * fix(plugins): address PR review findings on PyPI plugin migration Round 1 (blockers + high): - Restore exclude-newer = "10 days" in pyproject.toml; replace stale langchain/requests pins with cpex-* per-package overrides anchored to 2026-04-09 so the plugins resolve newer than the global window - Guard cpex_retry_with_backoff import in tool_service.py with try/except ImportError; falls back to (None, True) for the Python pipeline when the optional [plugins] extra is not installed - Delete orphaned .github/workflows/rust-plugins.yml and the associated test cases in tests/unit/test_rust_plugins_workflow.py; drop the workflow card from docs/docs/architecture/explorer.html - Delete orphaned docs/docs/using/plugins/rust-plugins.md and remove it from docs/docs/using/plugins/.pages mkdocs nav - Harden docker-entrypoint.sh install_plugin_requirements: canonicalize /app and the resolved requirements path with readlink -f and require the path to live under /app/, log non-comment lines from the requirements file before pip runs, and skip cleanly on validation failure - Delete PLUGIN-MIGRATION-PLAN.md (one-time planning doc) - Add COPY plugins/requirements.txt to Containerfile.scratch (the layered Containerfile.lite already had it; the broad COPY . in Containerfile already includes it) Round 2 (medium + low): - Bump cpex-* version pin floors in pyproject.toml [plugins] to match resolved versions in uv.lock (cpex-rate-limiter>=0.0.3, cpex-encoded-exfil-detection>=0.2.0, cpex-pii-filter>=0.2.0, cpex-url-reputation>=0.1.1) - Add Prerequisites section to tests/performance/PLUGIN_PROFILING.md documenting the [plugins] extra requirement - Add Status: Partially superseded note to ADR-041 explaining that plugins_rust/ was removed when in-tree Rust plugins migrated to PyPI packages - Document upgrade semantics in plugins/requirements.txt header (pip without --upgrade skips already-satisfied constraints) - Add importlib.util.find_spec() precheck to tests/performance/test_plugins_performance.py main(); the script now skips cleanly with an actionable message if any of the five cpex packages referenced by the perf config are missing - Rename tests/unit/test_rust_plugins_workflow.py to test_go_toolchain_pinning.py to match its remaining contents (Go workflow pin and Makefile toolchain assertion) Follow-ups tracked in #4116 and IBM/cpex-plugins#21 for the longer-term tool_service.py refactor that will eliminate the cross-package import entirely. Signed-off-by: Jonathan Springer <jps@s390x.com> Signed-off-by: lucarlig <luca.carlig@ibm.com> * revert: restore tests changes from PR #3965 Signed-off-by: lucarlig <luca.carlig@ibm.com> * fix(ci): align plugin tests with PyPI migration Signed-off-by: lucarlig <luca.carlig@ibm.com> * test: remove legacy plugin test skip infrastructure Signed-off-by: lucarlig <luca.carlig@ibm.com> * test: align packaged plugin tests with rust shims Signed-off-by: lucarlig <luca.carlig@ibm.com> * test: cover retry policy import path in tool service Signed-off-by: lucarlig <luca.carlig@ibm.com> * fix: harden cpex plugin migration paths Signed-off-by: lucarlig <luca.carlig@ibm.com> * test: cover retry policy parser branches Signed-off-by: lucarlig <luca.carlig@ibm.com> * test: cover plugin requirements entrypoint path Signed-off-by: lucarlig <luca.carlig@ibm.com> --------- Signed-off-by: lucarlig <luca.carlig@ibm.com> Signed-off-by: Jonathan Springer <jps@s390x.com> Co-authored-by: Pratik Gandhi <gandhipratik203@gmail.com> Co-authored-by: Lang-Akshay <akshay.shinde26@ibm.com> Co-authored-by: lucarlig <luca.carlig@ibm.com>
Summary
cpex-*)plugins_rust/Rust plugin implementations — plugins are now distributed as pre-built wheels from PyPItools_rust/mcp_runtime)rust-*plugin Makefile targets[plugins]extra toinstall-devand CI pytest workflow so cpex packages are installedtool_service.pyimport fromplugins.retry_with_backofftocpex_retry_with_backoffkindpaths across 7+ doc files fromplugins.pii_filter.pii_filter.PIIFilterPlugintocpex_pii_filter.PIIFilterPluginTest plan
make install-dev && make testpasses (cpex packages installed via[plugins]extra)--extra pluginsplugins_rust/directorytest_all_plugins_load_together) passes with correct count🤖 Generated with Claude Code