diff --git a/application/single_app/config.py b/application/single_app/config.py index e7b780d6..f1905806 100644 --- a/application/single_app/config.py +++ b/application/single_app/config.py @@ -95,7 +95,7 @@ EXECUTOR_TYPE = 'thread' EXECUTOR_MAX_WORKERS = 30 SESSION_TYPE = 'filesystem' -VERSION = "0.250.106" +VERSION = "0.250.107" IS_DEVELOPMENT = is_development_env_enabled() SESSION_COOKIE_SAMESITE = os.getenv('SESSION_COOKIE_SAMESITE', 'Lax') diff --git a/application/single_app/functions_simplechat_operations.py b/application/single_app/functions_simplechat_operations.py index 2e3aa49f..9bce1a37 100644 --- a/application/single_app/functions_simplechat_operations.py +++ b/application/single_app/functions_simplechat_operations.py @@ -79,6 +79,12 @@ "group-single-user", "public", } +FORKABLE_PERSONAL_CONTEXT_SCOPES = { + "", + "personal", + "model", + "model_knowledge", +} PERSONAL_FORK_CONVERSATION_FIELDS = ( "context", "tags", @@ -275,7 +281,7 @@ def _authorize_fork_conversation_context( raise ConversationForkConflictError("The conversation context is invalid") scope = str(context_item.get("scope") or "").strip().lower() - if scope in {"", "personal"}: + if scope in FORKABLE_PERSONAL_CONTEXT_SCOPES: authorized_scopes.add("personal") continue diff --git a/docs/explanation/fixes/PERSONAL_CONVERSATION_FORK_MODEL_CONTEXT_FIX.md b/docs/explanation/fixes/PERSONAL_CONVERSATION_FORK_MODEL_CONTEXT_FIX.md new file mode 100644 index 00000000..ddbc0e23 --- /dev/null +++ b/docs/explanation/fixes/PERSONAL_CONVERSATION_FORK_MODEL_CONTEXT_FIX.md @@ -0,0 +1,34 @@ +# Personal Conversation Fork Model Context Fix + +Fixed/Implemented in version: **0.250.107** + +## Issue Description + +Personal conversations that only had the built-in "Model's knowledge" context could be rejected during fork creation with an unsupported workspace context conflict, even though they were not group or public workspace conversations. + +## Root Cause Analysis + +The fork authorization helper revalidated every stored context as either personal, group, or public. Conversation metadata stores model knowledge as a secondary context with scope `Model` and id `N/A`, so the fork helper interpreted it as an unsupported workspace context instead of treating it as model-only personal context. + +## Technical Details + +Files modified: +- `application/single_app/functions_simplechat_operations.py` +- `application/single_app/config.py` +- `functional_tests/test_conversation_fork.py` + +Code changes summary: +- Added model and model-knowledge scopes to the personal fork context allow-list. +- Preserved group and public workspace revalidation behavior for real workspace contexts. +- Updated `config.py` from version `0.250.106` to `0.250.107` for traceability. + +Testing approach: +- Added a regression test covering personal forks whose only stored context is model knowledge. +- Covered both the stored `Model` scope with `N/A` id and the normalized `model_knowledge` form. + +## Validation + +Expected behavior after the fix: +- Personal model-only conversations can be forked successfully. +- The fork preserves the source model-knowledge context. +- Group and public workspace conversations still require the existing workspace availability and access checks. diff --git a/functional_tests/test_conversation_fork.py b/functional_tests/test_conversation_fork.py index ea83693d..4895fc23 100644 --- a/functional_tests/test_conversation_fork.py +++ b/functional_tests/test_conversation_fork.py @@ -2,13 +2,14 @@ # test_conversation_fork.py """ Functional test for personal conversation forking. -Version: 0.250.101 +Version: 0.250.107 Implemented in: 0.250.074 This test ensures persisted personal conversation history is copied through an assistant boundary with independent identifiers, deterministic ordering, -workspace-context authorization, concurrency protection, failed-write cleanup, -and stable conflict responses when structured logging is invoked. +workspace-context authorization, model-knowledge context handling, concurrency +protection, failed-write cleanup, and stable conflict responses when structured +logging is invoked. """ import ast @@ -22,6 +23,11 @@ import pytest from azure.cosmos.exceptions import CosmosResourceNotFoundError from flask import Blueprint, Flask, jsonify, request +import werkzeug + + +if not hasattr(werkzeug, '__version__'): + werkzeug.__version__ = '3' sys.path.insert( @@ -707,6 +713,32 @@ def test_fork_revalidates_and_preserves_workspace_context( assert test_state['result']['conversation']['context'] == [context] +@pytest.mark.parametrize( + ('model_scope', 'model_context_id'), + [ + ('Model', 'N/A'), + ('model_knowledge', None), + ], +) +def test_fork_allows_personal_model_knowledge_context(model_scope, model_context_id): + """Allow personal forks when the only stored context is model knowledge.""" + model_context = { + 'type': 'secondary', + 'scope': model_scope, + 'name': "Model's knowledge", + } + if model_context_id is not None: + model_context['id'] = model_context_id + + conversation = build_source_conversation(chat_type='personal') + conversation['context'] = [model_context] + + test_state = run_fork(source_conversation=conversation) + + assert test_state['result']['conversation']['chat_type'] == 'personal_single_user' + assert test_state['result']['conversation']['context'] == [model_context] + + @pytest.mark.parametrize( ('conversation', 'access_replacements', 'error_match'), [