fix: add per-route isolated tenant profile dimension to gateway routing#3190
fix: add per-route isolated tenant profile dimension to gateway routing#3190praisonai-triage-agent[bot] wants to merge 2 commits into
Conversation
β¦ng (fixes #3189) Adds an optional `profile` field to the core RouteBinding routing contract and surfaces the matched profile via RouteMatch/resolve_route. This gives each route a named isolation scope the wrapper can key its memory namespace / secret scope off, so one gateway can multiplex tenants. Resolution is most-specific-wins and fails closed: an unmatched route carries profile=None and never inherits another tenant's profile. Core-only, backward-compatible (all fields optional), no new deps/exports. Co-authored-by: MervinPraison <MervinPraison@users.noreply.github.com>
|
@coderabbitai review |
|
/review |
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more β On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
β Action performedReview finished.
|
|
Important Review skippedBot user detected. To trigger a single review, invoke the βοΈ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
π WalkthroughWalkthroughAdds optional per-route profile support to gateway routing contracts. Route configuration parses profiles, route resolution returns the matched profile, and unit tests verify defaults, coercion, propagation, unscoped bindings, and unmatched-route behavior. ChangesRoute profile isolation
Estimated code review effort: 2 (Simple) | ~10 minutes Suggested reviewers: π₯ Pre-merge checks | β 5β Passed checks (5 passed)
β¨ Finishing Touchesπ§ͺ Generate unit tests (beta)
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. Comment |
Greptile SummaryThis PR adds a minimal, backward-compatible
Confidence Score: 5/5Safe to merge β all new fields are optional with None defaults, the blank-profile normalisation guard is correct and tested, and the fail-closed fallback path is unchanged. The change is narrowly scoped to two dataclass fields and one line in resolve_route. The previously raised empty-profile normalisation concern is resolved by the post_init guard added in this PR, and the accompanying test explicitly covers from_dict with an empty profile string. No existing behaviour is altered. No files require special attention. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Inbound RouteFacts] --> B[resolve_route]
B --> C{Any binding\nmatches?}
C -- Yes --> D[Select highest-priority\nmost-specific binding]
D --> E[RouteMatch\nagent = binding.agent\nbinding = best\nprofile = best.profile]
E --> F{profile\nis None?}
F -- No --> G[Wrapper enters\ntenant profile scope\nmemory / secrets / home]
F -- Yes --> H[Wrapper skips profile scope\nroute is unscoped]
C -- No --> I[RouteMatch\nagent = default_agent\nbinding = None\nprofile = None fail-closed]
I --> H
subgraph RouteBinding.__post_init__
J[profile blank or whitespace?] -->|Yes| K[profile = None unscoped]
J -->|No| L[profile kept as-is]
end
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[Inbound RouteFacts] --> B[resolve_route]
B --> C{Any binding\nmatches?}
C -- Yes --> D[Select highest-priority\nmost-specific binding]
D --> E[RouteMatch\nagent = binding.agent\nbinding = best\nprofile = best.profile]
E --> F{profile\nis None?}
F -- No --> G[Wrapper enters\ntenant profile scope\nmemory / secrets / home]
F -- Yes --> H[Wrapper skips profile scope\nroute is unscoped]
C -- No --> I[RouteMatch\nagent = default_agent\nbinding = None\nprofile = None fail-closed]
I --> H
subgraph RouteBinding.__post_init__
J[profile blank or whitespace?] -->|Yes| K[profile = None unscoped]
J -->|No| L[profile kept as-is]
end
Reviews (2): Last reviewed commit: "fix: normalise blank route profile to No..." | Re-trigger Greptile |
|
@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAI (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on Python packages (praisonaiagents, praisonai). Do NOT modify praisonai-rust or praisonai-ts. Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding. Phase 1: Review per AGENTS.md
Phase 2: FIX Valid Issues Phase 3: Final Verdict |
|
Claude finished @MervinPraison's task ββ View job Final Architecture Review β PR #3190 β
Phase 1 β Architecture (per AGENTS.md)
Phase 2 β Valid issue fixedGreptile (p1, valid): Fix pushed (commit
Skipped: No other changes. CodeRabbit/Qodo raised no actionable items; Greptile's suggested test is subsumed by the added test. No wrapper isolation wiring added (correctly out of scope, no live consumer). Files modified
Tests
Phase 3 β Verdictβ
Approve. The change is minimal, protocol-driven, backward-compatible, and now consistently fail-closed for blank profiles. Ready to merge. |
Empty/whitespace-only profile strings were passing through _as_opt_str as non-None values, making them indistinguishable from a named scope and violating the documented 'None means unscoped' fail-closed contract. Mirror the existing trust normalisation in __post_init__ and add tests. Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>
Fixes #3189
Summary
Gateway routing could bind a chat to an agent but not to an isolated tenant profile, so memory/credentials/model were shared across every route. This adds the minimal core-protocol contract for per-route isolation β the 'secondary touch (core protocol)' the issue itself scopes as core's role β without building out the heavy wrapper subsystem (which would be scope creep with no live consumer).
Changes (core only, praisonaiagents/gateway/protocols.py)
Why minimal
Per AGENTS.md, this package stays lightweight and powerful. Rather than adding a ProfileScope/ProfileResolverProtocol and wrapper isolation wiring with no live consumer, this adds the smallest backward-compatible contract that unblocks tenant isolation. Heavy memory/secret/home wiring belongs in the wrapper and can build on this contract.
Tests
Added TestProfileIsolation in tests/unit/test_route_bindings.py (default None, from_dict parse + string-coercion, matched-profile surfacing, fail-closed on unmatched, unscoped binding). All 55 route tests pass.
Backward-compatible: all new fields are optional; no new dependencies or exports.
Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Tests