Skip to content

fix(cli): validate static components before dynamic plugins - #812

Merged
rapids-bot[bot] merged 2 commits into
NVIDIA:mainfrom
bbednarski9:bbednarski/fix-dynamic-switchyard-validation-order
Aug 20, 2026
Merged

fix(cli): validate static components before dynamic plugins#812
rapids-bot[bot] merged 2 commits into
NVIDIA:mainfrom
bbednarski9:bbednarski/fix-dynamic-switchyard-validation-order

Conversation

@bbednarski9

@bbednarski9 bbednarski9 commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Overview

Validate user-authored static components before the CLI synthesizes component specs for dynamic plugins. This prevents a dynamic plugin whose plugin_id is switchyard from being rejected as the removed legacy static Switchyard component.

This follow-up was rebased onto main after #811 merged and now contains only the isolated ordering fix.

  • I confirm this contribution is my own work, or I have the right to submit it under this project's license.
  • I searched existing issues and open pull requests, and this does not duplicate existing work.

Details

  • Run built-in component registration and legacy static-component validation against the parsed base PluginConfig.
  • Append synthesized dynamic component specs only after that static validation succeeds.
  • Preserve rejection of enabled and disabled legacy [[components]] kind = "switchyard" entries, with direct regression coverage for both states.
  • Add regression coverage showing dynamic plugin_id = "switchyard" reaches dynamic activation and receives its dynamic manifest diagnostic instead of the legacy migration error.

Validation completed successfully:

  • cargo test -p nemo-relay-cli --lib server::tests::register_and_validate_plugin_components_rejects_legacy_switchyard_components -- --exact (1 passed)
  • cargo test -p nemo-relay-cli --lib server::tests::plugin_activation_covers_empty_invalid_and_missing_manifest_paths -- --exact (1 passed after rebase)
  • cargo fmt --all -- --check
  • cargo clippy --workspace --all-targets -- -D warnings
  • just build-test-plugin-fixtures
  • just test-python-plugin (140 passed)
  • just test-rust
  • uv run pre-commit run --all-files

Breaking changes: none. Dynamic plugins continue to validate and activate through their registered plugin kinds; the change only prevents them from being mistaken for user-authored legacy static components.

Where should the reviewer start?

Start with the validation ordering in crates/cli/src/server/mod.rs, followed by the static and dynamic Switchyard regressions in crates/cli/tests/coverage/shared/server_tests.rs.

Related Issues: (use one of the action keywords Closes / Fixes / Resolves / Relates to)

Summary by CodeRabbit

  • Bug Fixes

    • Improved validation for plugin configurations before dynamic components are activated.
    • Legacy Switchyard components are now consistently rejected with a clear removal error.
    • Updated error reporting to identify unsupported native dynamic plugins without outdated removal messaging.
  • Tests

    • Expanded coverage for enabled and disabled legacy Switchyard components.
    • Added coverage for dynamic Rust plugins without manifests.

@coderabbitai

coderabbitai Bot commented Aug 19, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 5ada0d99-c653-4b43-95db-6e06c7c68cc8

📥 Commits

Reviewing files that changed from the base of the PR and between aeba7c6 and 1c4abb3.

📒 Files selected for processing (1)
  • crates/cli/tests/coverage/shared/server_tests.rs

Included review availability: Your plan provides up to 12 included reviews per hour; 10 remain after this review.

📜 Recent review details
⏰ Context from checks skipped due to timeout. (13)
  • GitHub Check: Rust / Package (windows-arm64)
  • GitHub Check: Rust / Test (macos-arm64)
  • GitHub Check: Rust / Test (linux-arm64)
  • GitHub Check: Rust / Package (windows-amd64)
  • GitHub Check: Rust / Package (linux-arm64)
  • GitHub Check: Rust / Package (linux-amd64)
  • GitHub Check: Rust / Package (macos-arm64)
  • GitHub Check: Rust / Package (linux-musl-amd64)
  • GitHub Check: Rust / Package (linux-musl-arm64)
  • GitHub Check: Rust / Test (linux-amd64)
  • GitHub Check: Rust / Test (windows-arm64)
  • GitHub Check: Rust / Test (windows-amd64)
  • GitHub Check: Check / Run
🧰 Additional context used
📓 Path-based instructions (15)
**/*.rs

📄 CodeRabbit inference engine (.agents/skills/test-ffi-surface/SKILL.md)

**/*.rs: Run cargo fmt --all for all FFI work since it is Rust work
Run just test-rust to validate FFI changes
Run cargo clippy --workspace --all-targets -- -D warnings to enforce strict linting on FFI work

When Rust files changed as part of Go work, also run cargo fmt --all, just test-rust, and cargo clippy --workspace --all-targets -- -D warnings

**/*.rs: Run cargo fmt --all when Rust files are changed as part of Node work
Run cargo clippy --workspace --all-targets -- -D warnings when Rust files are changed as part of Node work
Run just test-rust when Rust files are changed as part of Node work

**/*.rs: Use Json = serde_json::Value in Rust-facing runtime APIs where the existing code expects JSON payloads.
Use Result<T> with FlowError in core runtime paths. Keep errors explicit and binding-appropriate at the wrapper layer.

**/*.rs: Formatting: cargo fmt (rustfmt defaults)
Linting: cargo clippy -- -D warnings -- all warnings are treated as errors
Dependency auditing: cargo deny check -- configured in deny.toml

**/*.rs: If any Rust code changed, also run cargo fmt --all.
If any Rust code changed, also run cargo clippy --workspace --all-targets -- -D warnings.
Use test-rust-core. This always includes just test-rust,
cargo fmt --all, cargo clippy --workspace --all-targets -- -D warnings,
and the full matrix across Rust, Python, Go, and Node.js.

Files:

  • crates/cli/tests/coverage/shared/server_tests.rs
**/*.{rs,py,js,mjs,ts,go,c,h}

📄 CodeRabbit inference engine (AGENTS.md)

Keep SPDX headers on source, docs, scripts, and configuration files. The project is Apache-2.0.

Files:

  • crates/cli/tests/coverage/shared/server_tests.rs
**/*.{rs,py}

📄 CodeRabbit inference engine (AGENTS.md)

Follow binding naming conventions: Rust and Python snake_case, C FFI exports prefixed nemo_relay_, Go PascalCase for public APIs, Node.js camelCase.

Files:

  • crates/cli/tests/coverage/shared/server_tests.rs
**/*.{rs,py,js,mjs,ts}

📄 CodeRabbit inference engine (AGENTS.md)

Keep async behavior on the existing tokio-based model. Bindings should preserve callback and future lifetimes rather than blocking or hiding async work unexpectedly.

Files:

  • crates/cli/tests/coverage/shared/server_tests.rs
**/*.{rs,py,go,js,ts,html,md,mdx,toml}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

All source files must include an SPDX license header.

Files:

  • crates/cli/tests/coverage/shared/server_tests.rs
**/*.{rs,c,h}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Use the naming conventions appropriate to each language: Rust snake_case, C FFI exports prefixed nemo_relay_, Go PascalCase, Node.js camelCase, Python snake_case.

Files:

  • crates/cli/tests/coverage/shared/server_tests.rs
**/*.{rs,py,go,js,ts}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*.{rs,py,go,js,ts}: Run tests for every language affected by your changes. If your change touches the core Rust crate, run tests across all bindings since they all depend on it.
When adding new functionality, include tests in the appropriate test files for each affected language binding.

**/*.{rs,py,go,js,ts}: - [ ] Do all bindings expose the same logical knobs and semantics?

  • Does every OpenTelemetry endpoint require a type and nonblank destination?
  • Does each endpoint resolve header_env values at activation and reject
    missing, blank, or duplicate headers?
  • Are OpenTelemetry and OpenInference dependencies unconditional rather
    than Cargo feature-gated?
  • Does enable_full_payloads preserve complete sanitized LLM request input
    and annotations while leaving credential removal and sanitizers active?
  • Does Relay derive compliant trace and span IDs consistently across typed
    OpenTelemetry endpoints while preserving lifecycle parentage?
  • Are mark events, start/end events, and orphan cases still handled correctly?
  • Do examples and docs use each exporter's documented flush/deregister
    order before shutdown?
  • Run the affected Rust crate tests plus just test-rust if event
    fields changed.
  • Run just test-python, just test-go, and just test-node when
    binding-native config or lifecycle changed.

Files:

  • crates/cli/tests/coverage/shared/server_tests.rs
**/*

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*: Every commit in a pull request must include a Developer Certificate of Origin sign-off.
CI must pass before merging.
Use SONAR_IGNORE_START / SONAR_IGNORE_END only for documented false
positives that cannot be resolved in code or by improving the analyzer
configuration.
Keep the ignored block as small as possible, add a brief comment
explaining why the suppression is needed, and call it out in the PR description
so reviewers can explicitly sign off on it.
Keep the first line under 72 characters. Use the body for additional context when the change is not self-explanatory.

**/*: - [ ] Branch scope is coherent and reviewable

  • Relevant tests passed under validate-change

  • Docs and examples updated for any public behavior changes

  • Pull request title follows Conventional Commit style and uses the correct
    type
    Use Conventional Commit style for PR titles:
    Only check the contribution confirmation boxes when they are true. If either
    confirmation cannot be made, stop before opening the PR and surface the blocker.

  • SPDX license header on any new files

**/*: Tool execution callbacks and each execution-intercept next continuation
return the canonical ToolExecutionResult { result, annotation }. A forwarding
intercept must preserve both fields in ToolExecutionInterceptOutcome; Relay
retains pending_marks separately.
Tool sanitize-response guardrails receive
only result.

  • Registration and duplicate-name behavior
  • Deregistration and no-op missing-name behavior
  • Ordering by priority
  • Callback failure policy, including fail-open behavior when required
  • Scope-local registration, inheritance, and cleanup on pop
  • Parity coverage in every affected binding

**/*: Keep NeMo Relay optional
Use stable, documented framework or plugin APIs
Wrap tool and LLM paths at the correct framework boundary
Preserve the framework's original behavior when NeMo Relay is absent
Integration uses public framework or plugin A...

Files:

  • crates/cli/tests/coverage/shared/server_tests.rs
**/*.{rs,toml}

📄 CodeRabbit inference engine (.agents/skills/prepare-pr/SKILL.md)

**/*.{rs,toml}: - [ ] Any Rust change ran just test-rust

  • Any Rust change ran cargo fmt --all
  • Any Rust change ran cargo clippy --workspace --all-targets -- -D warnings

If any Rust code changed, always run just test-rust.

Files:

  • crates/cli/tests/coverage/shared/server_tests.rs
**/*.{rs,py,pyi,go,js,ts}

📄 CodeRabbit inference engine (.agents/skills/add-binding-feature/SKILL.md)

**/*.{rs,py,pyi,go,js,ts}: 6. Validation
Run the validation matrix from the validate-change skill for the affected
surfaces.

  • Tests added in every affected language surface

Files:

  • crates/cli/tests/coverage/shared/server_tests.rs
{crates,python}/**/*.{rs,py}

📄 CodeRabbit inference engine (.agents/skills/maintain-dynamic-plugins/SKILL.md)

Rust and Python SDKs expose every supported registration surface.

Files:

  • crates/cli/tests/coverage/shared/server_tests.rs
**/*.{md,mdx,rs,py,go,js,ts}

📄 CodeRabbit inference engine (.agents/skills/maintain-observability/SKILL.md)

  • Update docs and examples in the same branch.

Files:

  • crates/cli/tests/coverage/shared/server_tests.rs
**/*.{py,rs,go,js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

**/*.{py,rs,go,js,jsx,ts,tsx}: If a language surface changed, always run that language's test target even when
Rust core did not change.

Files:

  • crates/cli/tests/coverage/shared/server_tests.rs
**/*.{rs,h,c,cc,cpp}

📄 CodeRabbit inference engine (.agents/skills/validate-change/SKILL.md)

Use test-ffi-surface.

Files:

  • crates/cli/tests/coverage/shared/server_tests.rs
{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}

⚙️ CodeRabbit configuration file

{crates/**/tests/**,python/tests/**,go/nemo_relay/**/*_test.go}: Tests should cover the behavior promised by the changed API surface, including error paths and cross-request isolation where relevant.
Prefer assertions on lifecycle events, scope stacks, middleware ordering, and binding parity over shallow smoke tests.

Files:

  • crates/cli/tests/coverage/shared/server_tests.rs
🔇 Additional comments (1)
crates/cli/tests/coverage/shared/server_tests.rs (1)

1840-1861: LGTM!


Walkthrough

The plugin activation flow now validates static configuration before adding dynamic plugin components. Tests cover legacy switchyard rejection and dynamic switchyard native plugin handling.

Changes

Plugin validation

Layer / File(s) Summary
Validate configuration before dynamic activation
crates/cli/src/server/mod.rs, crates/cli/tests/coverage/shared/server_tests.rs
Static plugin configuration is validated before dynamic components are appended. Tests verify RemovedSwitchyard for legacy components and the native dynamic plugin error for dynamic switchyard activation.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to 1c4ab

This localized change validates static components before synthesizing dynamic plugin specifications, preserving legacy rejection while allowing dynamic plugins to activate correctly; no actionable merge-blocking risk remains after normal checks and review.

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Title check ✅ Passed The title follows Conventional Commits format, uses an allowed type and scope, stays under 72 characters, and summarizes the validation-ordering fix.
Description check ✅ Passed The description includes all required sections, explains the change, identifies review starting points, records validation, and lists related issues.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@bbednarski9

Copy link
Copy Markdown
Contributor Author

Follow-up: add regression coverage for the dynamic-plugin validation ordering regression, including dynamic activation of the Switchyard plugin while preserving rejection of legacy static kind = "switchyard" components.

@github-actions github-actions Bot added size:XXL PR is very large Bug issue describes bug; PR fixes bug lang:python PR changes/introduces Python code lang:rust PR changes/introduces Rust code labels Aug 19, 2026
Comment thread crates/cli/tests/coverage/shared/server_tests.rs
@bbednarski9 bbednarski9 added this to the 0.8 milestone Aug 19, 2026
@github-actions

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown

License Diff

Compared against origin/main.

Lockfile license changes

Lockfile License Changes

Rust

Added

  • None

Removed

  • switchyard-protocol 0.1.0 (Apache-2.0)
  • switchyard-translation 0.1.0 (Apache-2.0)

Updated/Changed

  • None

Node

Added

  • None

Removed

  • None

Updated/Changed

  • None

Python

Added

  • None

Removed

  • None

Updated/Changed

  • None
Status output
[license-diff] selected languages: rust, node, python
[license-diff] generating current inventory
[license-diff] current: generating Rust inventory
[license-diff] current: Rust inventory complete (446 packages)
[license-diff] current: generating Node inventory
[license-diff] current: Node inventory complete (367 packages)
[license-diff] current: generating Python inventory
[license-diff] current: Python inventory complete (105 packages)
[license-diff] current inventory complete
[license-diff] checking out base ref origin/main into a temporary worktree
[license-diff] base: generating Rust inventory
[license-diff] base: Rust inventory complete (448 packages)
[license-diff] base: generating Node inventory
[license-diff] base: Node inventory complete (367 packages)
[license-diff] base: generating Python inventory
[license-diff] base: Python inventory complete (105 packages)
[license-diff] base inventory complete
[license-diff] removing temporary base worktree
[license-diff] comparing inventories
[license-diff] rendering Markdown output
[license-diff] done

Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
@bbednarski9
bbednarski9 force-pushed the bbednarski/fix-dynamic-switchyard-validation-order branch from d66d94c to aeba7c6 Compare August 20, 2026 00:59
@github-actions github-actions Bot added size:S PR is small and removed size:XXL PR is very large lang:python PR changes/introduces Python code labels Aug 20, 2026
@bbednarski9
bbednarski9 marked this pull request as ready for review August 20, 2026 01:10
@bbednarski9
bbednarski9 requested a review from a team as a code owner August 20, 2026 01:10
Signed-off-by: Bryan Bednarski <bbednarski@nvidia.com>
@bbednarski9

Copy link
Copy Markdown
Contributor Author

/merge

@rapids-bot
rapids-bot Bot merged commit e310ab4 into NVIDIA:main Aug 20, 2026
47 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug issue describes bug; PR fixes bug lang:rust PR changes/introduces Rust code size:S PR is small

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants