Hotfix v6.0.1 into Main#592
Conversation
Summary of Changes - Hotfix v6.0.1OverviewThis hotfix v6.0.1 includes configuration updates, API behavior modifications, security enhancements, and UI adjustments across multiple components of the application. Key Changes by Category1. FastAPI Container & Prisma Caching
2. RTK Query Refetching Behavior
3. Model Management Security
4. Admin UI Menu
External Interface Changes
|
There was a problem hiding this comment.
Files selected (6)
- lib/api-base/fastApiContainer.ts (1)
- lib/docs/admin/deploy.md (2)
- lib/schema/configSchema.ts (1)
- lib/serve/rest-api/Dockerfile (2)
- lib/serve/rest-api/NODEENV_CACHE/.gitkeep (1)
- lib/serve/rest-api/PRISMA_CACHE/.gitkeep (1)
Files ignored due to filter (3)
- lib/user-interface/react/package.json
- lisa-sdk/pyproject.toml
- package.json
Files skipped from review due to trivial changes (2)
- lib/serve/rest-api/NODEENV_CACHE/.gitkeep
- lib/serve/rest-api/PRISMA_CACHE/.gitkeep
Review comments generated (4)
- Review: 4
- LGTM: 0
Tips
Chat with AI reviewer (/reviewbot)
- Reply on review comments left by this bot to ask follow-up questions. A review comment is a comment on a diff or a file.
- Invite the bot into a review comment chain by tagging
/reviewbotin a reply.
Code suggestions
- The bot may make code suggestions, but please review them carefully before committing since the line number ranges may be misaligned.
- You can edit the comment made by the bot and manually tweak the suggestion if it is slightly off.
Pausing incremental reviews
- Add
/reviewbot: ignoreanywhere in the PR description to pause further reviews from the bot.
There was a problem hiding this comment.
Files selected (1)
- lib/docs/admin/deploy.md (2)
Review comments generated (2)
- Review: 2
- LGTM: 0
Tips
Chat with AI reviewer (/reviewbot)
- Reply on review comments left by this bot to ask follow-up questions. A review comment is a comment on a diff or a file.
- Invite the bot into a review comment chain by tagging
/reviewbotin a reply.
Code suggestions
- The bot may make code suggestions, but please review them carefully before committing since the line number ranges may be misaligned.
- You can edit the comment made by the bot and manually tweak the suggestion if it is slightly off.
Pausing incremental reviews
- Add
/reviewbot: ignoreanywhere in the PR description to pause further reviews from the bot.
143c680 to
5a9ef2a
Compare
There was a problem hiding this comment.
Files selected (6)
- lib/api-base/fastApiContainer.ts (1)
- lib/docs/admin/deploy.md (2)
- lib/schema/configSchema.ts (1)
- lib/serve/rest-api/Dockerfile (2)
- lib/serve/rest-api/NODEENV_CACHE/.gitkeep (1)
- lib/serve/rest-api/PRISMA_CACHE/.gitkeep (1)
Files ignored due to filter (3)
- lib/user-interface/react/package.json
- lisa-sdk/pyproject.toml
- package.json
Files skipped from review due to trivial changes (2)
- lib/serve/rest-api/NODEENV_CACHE/.gitkeep
- lib/serve/rest-api/PRISMA_CACHE/.gitkeep
Review comments generated (5)
- Review: 5
- LGTM: 0
Tips
Chat with AI reviewer (/reviewbot)
- Reply on review comments left by this bot to ask follow-up questions. A review comment is a comment on a diff or a file.
- Invite the bot into a review comment chain by tagging
/reviewbotin a reply.
Code suggestions
- The bot may make code suggestions, but please review them carefully before committing since the line number ranges may be misaligned.
- You can edit the comment made by the bot and manually tweak the suggestion if it is slightly off.
Pausing incremental reviews
- Add
/reviewbot: ignoreanywhere in the PR description to pause further reviews from the bot.
There was a problem hiding this comment.
Files selected (8)
- lib/user-interface/react/src/shared/reducers/configuration.reducer.ts (1)
- lib/user-interface/react/src/shared/reducers/mcp-server.reducer.ts (1)
- lib/user-interface/react/src/shared/reducers/mcp-tools.reducer.ts (1)
- lib/user-interface/react/src/shared/reducers/model-management.reducer.ts (1)
- lib/user-interface/react/src/shared/reducers/prompt-templates.reducer.ts (1)
- lib/user-interface/react/src/shared/reducers/rag.reducer.ts (1)
- lib/user-interface/react/src/shared/reducers/session.reducer.ts (1)
- lib/user-interface/react/src/shared/reducers/user-preferences.reducer.ts (1)
Review comments generated (0)
- Review: 0
- LGTM: 0
Tips
Chat with AI reviewer (/reviewbot)
- Reply on review comments left by this bot to ask follow-up questions. A review comment is a comment on a diff or a file.
- Invite the bot into a review comment chain by tagging
/reviewbotin a reply.
Code suggestions
- The bot may make code suggestions, but please review them carefully before committing since the line number ranges may be misaligned.
- You can edit the comment made by the bot and manually tweak the suggestion if it is slightly off.
Pausing incremental reviews
- Add
/reviewbot: ignoreanywhere in the PR description to pause further reviews from the bot.
There was a problem hiding this comment.
Files selected (2)
- lambda/models/lambda_functions.py (6)
- test/lambda/test_models_lambda.py (9)
Review comments generated (14)
- Review: 14
- LGTM: 0
Tips
Chat with AI reviewer (/reviewbot)
- Reply on review comments left by this bot to ask follow-up questions. A review comment is a comment on a diff or a file.
- Invite the bot into a review comment chain by tagging
/reviewbotin a reply.
Code suggestions
- The bot may make code suggestions, but please review them carefully before committing since the line number ranges may be misaligned.
- You can edit the comment made by the bot and manually tweak the suggestion if it is slightly off.
Pausing incremental reviews
- Add
/reviewbot: ignoreanywhere in the PR description to pause further reviews from the bot.
| def get_admin_status_and_groups(request: Request) -> tuple[bool, list[str]]: | ||
| admin_status = False | ||
| user_groups = [] | ||
|
|
||
| if "aws.event" in request.scope: | ||
| event = request.scope["aws.event"] | ||
| try: | ||
| user_groups = get_groups(event) | ||
| admin_status = is_admin(event) | ||
| except Exception: | ||
| user_groups = [] | ||
| admin_status = False | ||
| return admin_status, user_groups |
There was a problem hiding this comment.
The get_admin_status_and_groups() function silently catches all exceptions and returns default values. This could mask legitimate errors (e.g., network issues, malformed event data). Consider logging the exception or being more specific about which exceptions to catch.
| def get_admin_status_and_groups(request: Request) -> tuple[bool, list[str]]: | |
| admin_status = False | |
| user_groups = [] | |
| if "aws.event" in request.scope: | |
| event = request.scope["aws.event"] | |
| try: | |
| user_groups = get_groups(event) | |
| admin_status = is_admin(event) | |
| except Exception: | |
| user_groups = [] | |
| admin_status = False | |
| return admin_status, user_groups | |
| def get_admin_status_and_groups(request: Request) -> tuple[bool, list[str]]: | |
| admin_status = False | |
| user_groups = [] | |
| if "aws.event" in request.scope: | |
| event = request.scope["aws.event"] | |
| try: | |
| user_groups = get_groups(event) | |
| admin_status = is_admin(event) | |
| except (KeyError, ValueError, TypeError): | |
| user_groups = [] | |
| admin_status = False | |
| return admin_status, user_groups |
| admin_status, _ = get_admin_status_and_groups(request) | ||
| if not admin_status: | ||
| raise HTTPException(status_code=403, detail="User does not have permission to create models.") |
There was a problem hiding this comment.
The authorization check uses a generic error message. Consider including more context about what permissions are required or suggesting next steps for the user.
|
|
||
| import boto3 | ||
| import pytest | ||
| from fastapi import HTTPException, Request |
There was a problem hiding this comment.
The HTTPException import is added here but should be verified that it's used consistently throughout the test file. Ensure this import is necessary for the test cases that follow.
| from models.lambda_functions import ( | ||
| app, | ||
| create_model, | ||
| delete_model, | ||
| get_admin_status_and_groups, | ||
| get_instances, | ||
| model_not_found_handler, | ||
| update_model, | ||
| user_error_handler, | ||
| validation_exception_handler, | ||
| ) |
There was a problem hiding this comment.
The import of get_admin_status_and_groups from models.lambda_functions is added here. This is appropriate for the new authorization tests, but verify that this function is actually exported from the module and not just used internally.
| """Test exception handlers.""" | ||
| from fastapi.encoders import jsonable_encoder | ||
| from fastapi.exceptions import RequestValidationError | ||
| from models.lambda_functions import model_not_found_handler, user_error_handler, validation_exception_handler | ||
|
|
There was a problem hiding this comment.
The imports have been moved from the function signature to inside the function body. While this works, ensure this pattern is consistent with the rest of the test file. The previous version had these imports at the module level within the function, which is acceptable for test isolation.
| with patch("models.lambda_functions.is_admin") as mock_is_admin, patch( | ||
| "models.lambda_functions.get_groups" | ||
| ) as mock_get_groups: | ||
| mock_is_admin.return_value = True | ||
| mock_get_groups.return_value = ["admin-group"] | ||
|
|
||
| admin_status, user_groups = get_admin_status_and_groups(mock_request) | ||
| assert admin_status is True | ||
| assert user_groups == ["admin-group"] | ||
|
|
||
| # Test with non-admin event | ||
| non_admin_event = { | ||
| "requestContext": { | ||
| "authorizer": { | ||
| "username": "regular-user", | ||
| "groups": json.dumps(["user-group"]), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| mock_request.scope = {"aws.event": non_admin_event} | ||
|
|
||
| with patch("models.lambda_functions.is_admin") as mock_is_admin, patch( | ||
| "models.lambda_functions.get_groups" | ||
| ) as mock_get_groups: | ||
| mock_is_admin.return_value = False | ||
| mock_get_groups.return_value = ["user-group"] | ||
|
|
||
| admin_status, user_groups = get_admin_status_and_groups(mock_request) | ||
| assert admin_status is False | ||
| assert user_groups == ["user-group"] | ||
|
|
||
| # Test with no event in scope | ||
| mock_request.scope = {} | ||
| admin_status, user_groups = get_admin_status_and_groups(mock_request) | ||
| assert admin_status is False | ||
| assert user_groups == [] |
There was a problem hiding this comment.
The test patches is_admin and get_groups functions, but these are being mocked at the module level. Verify that these functions are actually called within get_admin_status_and_groups in the implementation, otherwise the mocks won't validate the actual behavior.
| async def test_create_model_admin_required( | ||
| sample_model, model_table, mock_autoscaling_client, mock_stepfunctions_client, admin_event, non_admin_event | ||
| ): | ||
| """Test that create_model endpoint requires admin access.""" | ||
|
|
||
| # Test non-admin cannot create | ||
| mock_request = MagicMock(spec=Request) | ||
| mock_request.scope = {"aws.event": non_admin_event} | ||
|
|
||
| create_request = CreateModelRequest( | ||
| modelId="test-model", modelName="test-model", modelType=ModelType.TEXTGEN, streaming=True | ||
| ) | ||
|
|
||
| with patch("models.lambda_functions.is_admin") as mock_is_admin, patch( | ||
| "models.lambda_functions.get_groups" | ||
| ) as mock_get_groups: | ||
| mock_is_admin.return_value = False | ||
| mock_get_groups.return_value = [] | ||
|
|
||
| with pytest.raises(HTTPException) as exc_info: | ||
| await create_model(create_request, mock_request) | ||
| assert exc_info.value.status_code == 403 | ||
| assert "User does not have permission to create models" in str(exc_info.value.detail) |
There was a problem hiding this comment.
The test for test_create_model_admin_required patches is_admin and get_groups but doesn't verify that get_admin_status_and_groups is actually called. Consider adding a verification that the helper function is invoked:
| async def test_create_model_admin_required( | |
| sample_model, model_table, mock_autoscaling_client, mock_stepfunctions_client, admin_event, non_admin_event | |
| ): | |
| """Test that create_model endpoint requires admin access.""" | |
| # Test non-admin cannot create | |
| mock_request = MagicMock(spec=Request) | |
| mock_request.scope = {"aws.event": non_admin_event} | |
| create_request = CreateModelRequest( | |
| modelId="test-model", modelName="test-model", modelType=ModelType.TEXTGEN, streaming=True | |
| ) | |
| with patch("models.lambda_functions.is_admin") as mock_is_admin, patch( | |
| "models.lambda_functions.get_groups" | |
| ) as mock_get_groups: | |
| mock_is_admin.return_value = False | |
| mock_get_groups.return_value = [] | |
| with pytest.raises(HTTPException) as exc_info: | |
| await create_model(create_request, mock_request) | |
| assert exc_info.value.status_code == 403 | |
| assert "User does not have permission to create models" in str(exc_info.value.detail) | |
| with patch("models.lambda_functions.is_admin") as mock_is_admin, patch( | |
| "models.lambda_functions.get_groups" | |
| ) as mock_get_groups, patch( | |
| "models.lambda_functions.get_admin_status_and_groups" | |
| ) as mock_get_admin_status: | |
| mock_is_admin.return_value = False | |
| mock_get_groups.return_value = [] | |
| mock_get_admin_status.return_value = (False, []) | |
| with pytest.raises(HTTPException) as exc_info: | |
| await create_model(create_request, mock_request) | |
| assert exc_info.value.status_code == 403 | |
| assert "User does not have permission to create models" in str(exc_info.value.detail) | |
| mock_get_admin_status.assert_called_once_with(mock_request) |
| async def test_update_model_admin_required( | ||
| sample_model, model_table, mock_autoscaling_client, mock_stepfunctions_client, admin_event, non_admin_event | ||
| ): | ||
| """Test that update_model endpoint requires admin access.""" | ||
|
|
||
| # Test non-admin cannot update | ||
| mock_request = MagicMock(spec=Request) | ||
| mock_request.scope = {"aws.event": non_admin_event} | ||
|
|
||
| update_request = UpdateModelRequest(streaming=False) | ||
|
|
||
| with patch("models.lambda_functions.is_admin") as mock_is_admin, patch( | ||
| "models.lambda_functions.get_groups" | ||
| ) as mock_get_groups: | ||
| mock_is_admin.return_value = False | ||
| mock_get_groups.return_value = [] | ||
|
|
||
| with pytest.raises(HTTPException) as exc_info: | ||
| await update_model("test-model", update_request, mock_request) | ||
| assert exc_info.value.status_code == 403 | ||
| assert "User does not have permission to update models" in str(exc_info.value.detail) |
There was a problem hiding this comment.
Similar to the create model test, consider mocking get_admin_status_and_groups directly and verifying it's called, rather than mocking its internal dependencies.
| async def test_delete_model_admin_required( | ||
| sample_model, model_table, mock_autoscaling_client, mock_stepfunctions_client, admin_event, non_admin_event | ||
| ): | ||
| """Test that delete_model endpoint requires admin access.""" | ||
|
|
||
| # Test non-admin cannot delete | ||
| mock_request = MagicMock(spec=Request) | ||
| mock_request.scope = {"aws.event": non_admin_event} | ||
|
|
||
| with patch("models.lambda_functions.is_admin") as mock_is_admin, patch( | ||
| "models.lambda_functions.get_groups" | ||
| ) as mock_get_groups: | ||
| mock_is_admin.return_value = False | ||
| mock_get_groups.return_value = [] | ||
|
|
||
| with pytest.raises(HTTPException) as exc_info: | ||
| await delete_model("test-model", mock_request) | ||
| assert exc_info.value.status_code == 403 | ||
| assert "User does not have permission to delete models" in str(exc_info.value.detail) |
There was a problem hiding this comment.
Same pattern as above - consider mocking get_admin_status_and_groups directly for consistency and clearer test intent.
| async def test_create_update_delete_admin_allowed( | ||
| sample_model, model_table, mock_autoscaling_client, mock_stepfunctions_client, admin_event | ||
| ): | ||
| """Test that admin users can successfully create, update, and delete models.""" | ||
|
|
||
| mock_request = MagicMock(spec=Request) | ||
| mock_request.scope = {"aws.event": admin_event} | ||
|
|
||
| with patch("models.lambda_functions.is_admin") as mock_is_admin, patch( | ||
| "models.lambda_functions.get_groups" | ||
| ) as mock_get_groups, patch("models.lambda_functions.CreateModelHandler") as mock_create_handler, patch( | ||
| "models.lambda_functions.UpdateModelHandler" | ||
| ) as mock_update_handler, patch( | ||
| "models.lambda_functions.DeleteModelHandler" | ||
| ) as mock_delete_handler: | ||
| mock_is_admin.return_value = True | ||
| mock_get_groups.return_value = ["admin-group"] | ||
|
|
||
| # Mock create handler | ||
| create_handler_instance = MagicMock() | ||
| create_model_response = CreateModelResponse( | ||
| model=LISAModel( | ||
| modelId="new-model", | ||
| modelName="new-model-name", | ||
| modelType=ModelType.TEXTGEN, | ||
| status=ModelStatus.CREATING, | ||
| streaming=True, | ||
| ) | ||
| ) | ||
| create_handler_instance.return_value = create_model_response | ||
| mock_create_handler.return_value = create_handler_instance | ||
|
|
||
| # Test admin can create | ||
| create_request = CreateModelRequest( | ||
| modelId="test-model", modelName="test-model", modelType=ModelType.TEXTGEN, streaming=True | ||
| ) | ||
| response = await create_model(create_request, mock_request) | ||
| assert isinstance(response, CreateModelResponse) | ||
| assert response.model.modelId == "new-model" | ||
|
|
||
| # Mock update handler | ||
| model_table.put_item(Item=sample_model) | ||
| update_handler_instance = MagicMock() | ||
| update_model_response = UpdateModelResponse( | ||
| model=LISAModel( | ||
| modelId="test-model", | ||
| modelName="gpt-3.5-turbo", | ||
| modelType=ModelType.TEXTGEN, | ||
| status=ModelStatus.IN_SERVICE, | ||
| streaming=False, | ||
| features=[{"name": "test-feature", "overview": "This is a test feature"}], | ||
| ) | ||
| ) | ||
| update_handler_instance.return_value = update_model_response | ||
| mock_update_handler.return_value = update_handler_instance | ||
|
|
||
| # Test admin can update | ||
| update_request = UpdateModelRequest(streaming=False) | ||
| response = await update_model("test-model", update_request, mock_request) | ||
| assert isinstance(response, UpdateModelResponse) | ||
| assert response.model.modelId == "test-model" | ||
|
|
||
| # Mock delete handler | ||
| delete_handler_instance = MagicMock() | ||
| delete_model_response = DeleteModelResponse( | ||
| model=LISAModel( | ||
| modelId="test-model", | ||
| modelName="gpt-3.5-turbo", | ||
| modelType=ModelType.TEXTGEN, | ||
| status=ModelStatus.DELETING, | ||
| streaming=True, | ||
| features=[{"name": "test-feature", "overview": "This is a test feature"}], | ||
| ) | ||
| ) | ||
| delete_handler_instance.return_value = delete_model_response | ||
| mock_delete_handler.return_value = delete_handler_instance | ||
|
|
||
| # Test admin can delete | ||
| response = await delete_model("test-model", mock_request) | ||
| assert isinstance(response, DeleteModelResponse) | ||
| assert response.model.modelId == "test-model" | ||
| assert response.model.status == ModelStatus.DELETING |
There was a problem hiding this comment.
The test test_create_update_delete_admin_allowed mocks is_admin and get_groups separately rather than using get_admin_status_and_groups. For consistency with the authorization tests above, consider using the helper function mock directly:
| async def test_create_update_delete_admin_allowed( | |
| sample_model, model_table, mock_autoscaling_client, mock_stepfunctions_client, admin_event | |
| ): | |
| """Test that admin users can successfully create, update, and delete models.""" | |
| mock_request = MagicMock(spec=Request) | |
| mock_request.scope = {"aws.event": admin_event} | |
| with patch("models.lambda_functions.is_admin") as mock_is_admin, patch( | |
| "models.lambda_functions.get_groups" | |
| ) as mock_get_groups, patch("models.lambda_functions.CreateModelHandler") as mock_create_handler, patch( | |
| "models.lambda_functions.UpdateModelHandler" | |
| ) as mock_update_handler, patch( | |
| "models.lambda_functions.DeleteModelHandler" | |
| ) as mock_delete_handler: | |
| mock_is_admin.return_value = True | |
| mock_get_groups.return_value = ["admin-group"] | |
| # Mock create handler | |
| create_handler_instance = MagicMock() | |
| create_model_response = CreateModelResponse( | |
| model=LISAModel( | |
| modelId="new-model", | |
| modelName="new-model-name", | |
| modelType=ModelType.TEXTGEN, | |
| status=ModelStatus.CREATING, | |
| streaming=True, | |
| ) | |
| ) | |
| create_handler_instance.return_value = create_model_response | |
| mock_create_handler.return_value = create_handler_instance | |
| # Test admin can create | |
| create_request = CreateModelRequest( | |
| modelId="test-model", modelName="test-model", modelType=ModelType.TEXTGEN, streaming=True | |
| ) | |
| response = await create_model(create_request, mock_request) | |
| assert isinstance(response, CreateModelResponse) | |
| assert response.model.modelId == "new-model" | |
| # Mock update handler | |
| model_table.put_item(Item=sample_model) | |
| update_handler_instance = MagicMock() | |
| update_model_response = UpdateModelResponse( | |
| model=LISAModel( | |
| modelId="test-model", | |
| modelName="gpt-3.5-turbo", | |
| modelType=ModelType.TEXTGEN, | |
| status=ModelStatus.IN_SERVICE, | |
| streaming=False, | |
| features=[{"name": "test-feature", "overview": "This is a test feature"}], | |
| ) | |
| ) | |
| update_handler_instance.return_value = update_model_response | |
| mock_update_handler.return_value = update_handler_instance | |
| # Test admin can update | |
| update_request = UpdateModelRequest(streaming=False) | |
| response = await update_model("test-model", update_request, mock_request) | |
| assert isinstance(response, UpdateModelResponse) | |
| assert response.model.modelId == "test-model" | |
| # Mock delete handler | |
| delete_handler_instance = MagicMock() | |
| delete_model_response = DeleteModelResponse( | |
| model=LISAModel( | |
| modelId="test-model", | |
| modelName="gpt-3.5-turbo", | |
| modelType=ModelType.TEXTGEN, | |
| status=ModelStatus.DELETING, | |
| streaming=True, | |
| features=[{"name": "test-feature", "overview": "This is a test feature"}], | |
| ) | |
| ) | |
| delete_handler_instance.return_value = delete_model_response | |
| mock_delete_handler.return_value = delete_handler_instance | |
| # Test admin can delete | |
| response = await delete_model("test-model", mock_request) | |
| assert isinstance(response, DeleteModelResponse) | |
| assert response.model.modelId == "test-model" | |
| assert response.model.status == ModelStatus.DELETING | |
| with patch("models.lambda_functions.get_admin_status_and_groups") as mock_get_admin_status, patch("models.lambda_functions.CreateModelHandler") as mock_create_handler, patch( | |
| "models.lambda_functions.UpdateModelHandler" | |
| ) as mock_update_handler, patch( | |
| "models.lambda_functions.DeleteModelHandler" | |
| ) as mock_delete_handler: | |
| mock_get_admin_status.return_value = (True, ["admin-group"]) |
There was a problem hiding this comment.
Files selected (1)
- cypress/src/support/adminHelpers.ts (1)
Review comments generated (0)
- Review: 0
- LGTM: 0
Tips
Chat with AI reviewer (/reviewbot)
- Reply on review comments left by this bot to ask follow-up questions. A review comment is a comment on a diff or a file.
- Invite the bot into a review comment chain by tagging
/reviewbotin a reply.
Code suggestions
- The bot may make code suggestions, but please review them carefully before committing since the line number ranges may be misaligned.
- You can edit the comment made by the bot and manually tweak the suggestion if it is slightly off.
Pausing incremental reviews
- Add
/reviewbot: ignoreanywhere in the PR description to pause further reviews from the bot.
Hotfix v6.0.1 PR into Main
Summary (generated)
Summary of Changes - Hotfix v6.0.1
Overview
This hotfix v6.0.1 includes configuration updates, API behavior modifications, security enhancements, and UI adjustments across multiple components of the application.
Key Changes by Category
1. FastAPI Container & Prisma Caching
lib/api-base/fastApiContainer.ts,lib/schema/configSchema.ts,lib/serve/rest-api/DockerfileNODEENV_CACHE_DIRwithPRISMA_CACHE_DIRenvironment variable~/.cache/prisma/and~/.cache/prisma-python/)2. RTK Query Refetching Behavior
lib/user-interface/react/src/shared/reducers/configuration.reducer.tsmcp-server.reducer.tsmcp-tools.reducer.tsmodel-management.reducer.tsprompt-templates.reducer.tsrag.reducer.tssession.reducer.tsuser-preferences.reducer.tsrefetchOnReconnect: truewithrefetchOnMountOrArgChange: true3. Model Management Security
lambda/models/lambda_functions.py,test/lambda/test_models_lambda.pyget_admin_status_and_groups()utility function4. Admin UI Menu
cypress/src/support/adminHelpers.tsExternal Interface Changes
PRISMA_CACHE_DIRinstead ofNODEENV_CACHE_DIR