From 1c68a0fb8e87729e592c6c88883a5af7425a7842 Mon Sep 17 00:00:00 2001 From: Copilot Date: Fri, 27 Mar 2026 05:46:30 +0000 Subject: [PATCH 01/17] Harden Python checkpoint persistence defaults Add RestrictedUnpickler to _checkpoint_encoding.py that limits which types may be instantiated during pickle deserialization. By default FileCheckpointStorage now uses the restricted unpickler, allowing only: - Built-in Python value types (primitives, datetime, uuid, decimal, collections, etc.) - All agent_framework.* internal types - Additional types specified via the new allowed_checkpoint_types parameter on FileCheckpointStorage This narrows the default type surface area for persisted checkpoints while keeping framework-owned scenarios working without extra configuration. Developers can extend the allowed set by passing "module:qualname" strings to allowed_checkpoint_types. The decode_checkpoint_value function retains backward-compatible unrestricted behavior when called without the new allowed_types kwarg. Fixes #4894 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../agent_framework/_workflows/_checkpoint.py | 41 +++- .../_workflows/_checkpoint_encoding.py | 124 +++++++++-- .../core/tests/workflow/test_checkpoint.py | 18 +- .../test_checkpoint_unrestricted_pickle.py | 201 ++++++++++++++++++ .../test_request_info_event_rehydrate.py | 36 +++- 5 files changed, 390 insertions(+), 30 deletions(-) create mode 100644 python/packages/core/tests/workflow/test_checkpoint_unrestricted_pickle.py diff --git a/python/packages/core/agent_framework/_workflows/_checkpoint.py b/python/packages/core/agent_framework/_workflows/_checkpoint.py index b442f445f8..fc4ca4b159 100644 --- a/python/packages/core/agent_framework/_workflows/_checkpoint.py +++ b/python/packages/core/agent_framework/_workflows/_checkpoint.py @@ -244,14 +244,39 @@ class FileCheckpointStorage: is serialized using pickle and embedded as base64-encoded strings within the JSON. This allows for human-readable checkpoint files while preserving the ability to store complex Python objects. - SECURITY WARNING: Checkpoints use pickle for data serialization. Only load checkpoints - from trusted sources. Loading a malicious checkpoint file can execute arbitrary code. + By default, checkpoint deserialization is restricted to a built-in set of safe + Python types (primitives, datetime, uuid, ...) and all ``agent_framework`` + internal types. To allow additional application-specific types, pass them via + the ``allowed_checkpoint_types`` parameter using ``"module:qualname"`` format. + + Example:: + + storage = FileCheckpointStorage( + "/tmp/checkpoints", + allowed_checkpoint_types=[ + "my_app.models:SafeState", + ], + ) """ - def __init__(self, storage_path: str | Path): - """Initialize the file storage.""" + def __init__( + self, + storage_path: str | Path, + *, + allowed_checkpoint_types: list[str] | None = None, + ): + """Initialize the file storage. + + Args: + storage_path: Directory path where checkpoint files will be stored. + allowed_checkpoint_types: Additional types (beyond the built-in safe set + and framework types) that are permitted during checkpoint + deserialization. Each entry should be a ``"module:qualname"`` + string (e.g., ``"my_app.models:SafeState"``). + """ self.storage_path = Path(storage_path) self.storage_path.mkdir(parents=True, exist_ok=True) + self._allowed_types: frozenset[str] = frozenset(allowed_checkpoint_types or []) logger.info(f"Initialized file checkpoint storage at {self.storage_path}") def _validate_file_path(self, checkpoint_id: CheckpointID) -> Path: @@ -327,7 +352,9 @@ def _read() -> dict[str, Any]: from ._checkpoint_encoding import decode_checkpoint_value try: - decoded_checkpoint_dict = decode_checkpoint_value(encoded_checkpoint) + decoded_checkpoint_dict = decode_checkpoint_value( + encoded_checkpoint, allowed_types=self._allowed_types + ) except WorkflowCheckpointException: raise checkpoint = WorkflowCheckpoint.from_dict(decoded_checkpoint_dict) @@ -352,7 +379,9 @@ def _list_checkpoints() -> list[WorkflowCheckpoint]: encoded_checkpoint = json.load(f) from ._checkpoint_encoding import decode_checkpoint_value - decoded_checkpoint_dict = decode_checkpoint_value(encoded_checkpoint) + decoded_checkpoint_dict = decode_checkpoint_value( + encoded_checkpoint, allowed_types=self._allowed_types + ) checkpoint = WorkflowCheckpoint.from_dict(decoded_checkpoint_dict) if checkpoint.workflow_name == workflow_name: checkpoints.append(checkpoint) diff --git a/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py b/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py index 85f9327663..e9b26aece0 100644 --- a/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py +++ b/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py @@ -3,6 +3,7 @@ from __future__ import annotations import base64 +import io import logging import pickle # nosec # noqa: S403 from typing import Any @@ -16,8 +17,12 @@ - Full Python object fidelity via pickle for data values (non-JSON-native types) - Base64 encoding to embed binary pickle data in JSON strings -SECURITY WARNING: Checkpoints use pickle for data serialization. Only load checkpoints -from trusted sources. Loading a malicious checkpoint file can execute arbitrary code. +When ``allowed_types`` is supplied to :func:`decode_checkpoint_value`, a +``RestrictedUnpickler`` is used that limits which classes may be instantiated +during deserialization. The default built-in safe set covers common Python +value types (primitives, datetime, uuid, ...) and all ``agent_framework`` +internal types. Callers can extend the set by passing additional +``"module:qualname"`` strings. """ @@ -30,6 +35,78 @@ # Types that are natively JSON-serializable and don't need pickling _JSON_NATIVE_TYPES = (str, int, float, bool, type(None)) +# Module prefix for framework-internal types that are always allowed +_FRAMEWORK_MODULE_PREFIX = "agent_framework." + +# Built-in types considered safe for checkpoint deserialization. +# Each entry is a ``module:qualname`` string matching the format produced by +# :func:`_type_to_key`. These are the classes for which pickle's +# ``find_class`` will be called when unpickling common Python value types. +_BUILTIN_ALLOWED_TYPE_KEYS: frozenset[str] = frozenset({ + # builtins + "builtins:object", + "builtins:complex", + "builtins:range", + "builtins:slice", + "builtins:int", + "builtins:float", + "builtins:str", + "builtins:bytes", + "builtins:bytearray", + "builtins:bool", + "builtins:set", + "builtins:frozenset", + "builtins:list", + "builtins:dict", + "builtins:tuple", + "builtins:type", + # copyreg helpers used by pickle for object reconstruction + "copyreg:_reconstructor", + # datetime + "datetime:datetime", + "datetime:date", + "datetime:time", + "datetime:timedelta", + "datetime:timezone", + # uuid + "uuid:UUID", + # decimal + "decimal:Decimal", + # collections + "collections:OrderedDict", + "collections:defaultdict", + "collections:deque", +}) + + +class _RestrictedUnpickler(pickle.Unpickler): + """Unpickler that restricts which classes may be instantiated. + + Only classes whose ``module:qualname`` key appears in the combined allow + set (built-in safe types + framework types + caller-specified extras) are + permitted. All other classes raise :class:`WorkflowCheckpointException`. + """ + + def __init__(self, data: bytes, allowed_types: frozenset[str]) -> None: + super().__init__(io.BytesIO(data)) + self._allowed_types = allowed_types + + def find_class(self, module: str, name: str) -> type: + type_key = f"{module}:{name}" + + if ( + type_key in _BUILTIN_ALLOWED_TYPE_KEYS + or type_key in self._allowed_types + or module.startswith(_FRAMEWORK_MODULE_PREFIX) + ): + return super().find_class(module, name) + + raise WorkflowCheckpointException( + f"Checkpoint deserialization blocked for type '{type_key}'. " + f"To allow this type, add it to 'allowed_checkpoint_types' on " + f"your checkpoint storage." + ) + def encode_checkpoint_value(value: Any) -> Any: """Encode a Python value for checkpoint storage. @@ -48,29 +125,30 @@ def encode_checkpoint_value(value: Any) -> Any: return _encode(value) -def decode_checkpoint_value(value: Any) -> Any: +def decode_checkpoint_value(value: Any, *, allowed_types: frozenset[str] | None = None) -> Any: """Decode a value from checkpoint storage. Reverses the encoding performed by encode_checkpoint_value. Pickled values (identified by _PICKLE_MARKER) are decoded and unpickled. - WARNING: Only call this with trusted data. Pickle can execute - arbitrary code during deserialization. The post-unpickle type verification - detects accidental corruption or type mismatches, but cannot prevent - arbitrary code execution from malicious pickle payloads. - Args: value: A JSON-deserialized value from checkpoint storage. + allowed_types: If not ``None``, restrict pickle deserialization to the + built-in safe set, framework types, and the types listed here. + Each entry should use ``"module:qualname"`` format (e.g., + ``"my_app.models:SafeState"``). If ``None``, no restriction is + applied (backward-compatible behavior). Returns: The original Python value. Raises: WorkflowCheckpointException: If the unpickled object's type doesn't match - the recorded type, indicating corruption, or if the base64/pickle - data is malformed. + the recorded type, indicating corruption, if the base64/pickle + data is malformed, or if a disallowed type is encountered during + restricted deserialization. """ - return _decode(value) + return _decode(value, allowed_types=allowed_types) def _encode(value: Any) -> Any: @@ -94,7 +172,7 @@ def _encode(value: Any) -> Any: } -def _decode(value: Any) -> Any: +def _decode(value: Any, *, allowed_types: frozenset[str] | None = None) -> Any: """Recursively decode a value from JSON storage.""" # JSON-native types pass through if isinstance(value, _JSON_NATIVE_TYPES): @@ -104,16 +182,16 @@ def _decode(value: Any) -> Any: if isinstance(value, dict): # Pickled value: decode, unpickle, and verify type if _PICKLE_MARKER in value and _TYPE_MARKER in value: - obj = _base64_to_unpickle(value[_PICKLE_MARKER]) # type: ignore + obj = _base64_to_unpickle(value[_PICKLE_MARKER], allowed_types=allowed_types) # type: ignore _verify_type(obj, value.get(_TYPE_MARKER)) # type: ignore return obj # Regular dict: decode values recursively - return {k: _decode(v) for k, v in value.items()} # type: ignore + return {k: _decode(v, allowed_types=allowed_types) for k, v in value.items()} # type: ignore # Handle encoded lists if isinstance(value, list): - return [_decode(item) for item in value] # type: ignore + return [_decode(item, allowed_types=allowed_types) for item in value] # type: ignore return value @@ -148,16 +226,26 @@ def _pickle_to_base64(value: Any) -> str: return base64.b64encode(pickled).decode("ascii") -def _base64_to_unpickle(encoded: str) -> Any: +def _base64_to_unpickle(encoded: str, *, allowed_types: frozenset[str] | None = None) -> Any: """Decode base64 string and unpickle. + Args: + encoded: Base64-encoded pickle data. + allowed_types: If not ``None``, use restricted unpickling that only + permits built-in safe types, framework types, and the specified + extra types. + Raises: - WorkflowCheckpointException: If the base64 data is corrupted or the pickle - format is incompatible. + WorkflowCheckpointException: If the base64 data is corrupted, the pickle + format is incompatible, or a disallowed type is encountered. """ try: pickled = base64.b64decode(encoded.encode("ascii")) + if allowed_types is not None: + return _RestrictedUnpickler(pickled, allowed_types).load() return pickle.loads(pickled) # nosec # noqa: S301 + except WorkflowCheckpointException: + raise except Exception as exc: raise WorkflowCheckpointException(f"Failed to decode pickled checkpoint data: {exc}") from exc diff --git a/python/packages/core/tests/workflow/test_checkpoint.py b/python/packages/core/tests/workflow/test_checkpoint.py index a32489acc0..e395655afa 100644 --- a/python/packages/core/tests/workflow/test_checkpoint.py +++ b/python/packages/core/tests/workflow/test_checkpoint.py @@ -1048,7 +1048,10 @@ async def test_file_checkpoint_storage_roundtrip_datetime(): async def test_file_checkpoint_storage_roundtrip_dataclass(): """Test that dataclass objects roundtrip correctly via pickle encoding.""" with tempfile.TemporaryDirectory() as temp_dir: - storage = FileCheckpointStorage(temp_dir) + storage = FileCheckpointStorage( + temp_dir, + allowed_checkpoint_types=["tests.workflow.test_checkpoint:_TestCustomData"], + ) custom_obj = _TestCustomData(name="test", value=42, tags=["a", "b", "c"]) @@ -1238,7 +1241,10 @@ async def test_file_checkpoint_storage_roundtrip_messages_with_complex_data(): async def test_file_checkpoint_storage_roundtrip_pending_request_info_events(): """Test that pending_request_info_events with WorkflowEvent objects roundtrip correctly.""" with tempfile.TemporaryDirectory() as temp_dir: - storage = FileCheckpointStorage(temp_dir) + storage = FileCheckpointStorage( + temp_dir, + allowed_checkpoint_types=["tests.workflow.test_checkpoint:_TestToolApprovalRequest"], + ) # Create request_info events using the proper WorkflowEvent factory event1 = WorkflowEvent.request_info( @@ -1300,7 +1306,13 @@ async def test_file_checkpoint_storage_roundtrip_pending_request_info_events(): async def test_file_checkpoint_storage_roundtrip_full_checkpoint(): """Test complete WorkflowCheckpoint roundtrip with all fields populated using proper types.""" with tempfile.TemporaryDirectory() as temp_dir: - storage = FileCheckpointStorage(temp_dir) + storage = FileCheckpointStorage( + temp_dir, + allowed_checkpoint_types=[ + "tests.workflow.test_checkpoint:_TestApprovalRequest", + "tests.workflow.test_checkpoint:_TestExecutorState", + ], + ) # Create proper WorkflowMessage objects msg1 = WorkflowMessage(data="msg1", source_id="s", target_id="t") diff --git a/python/packages/core/tests/workflow/test_checkpoint_unrestricted_pickle.py b/python/packages/core/tests/workflow/test_checkpoint_unrestricted_pickle.py new file mode 100644 index 0000000000..e64e2ba617 --- /dev/null +++ b/python/packages/core/tests/workflow/test_checkpoint_unrestricted_pickle.py @@ -0,0 +1,201 @@ +# Copyright (c) Microsoft. All rights reserved. + +"""Tests for restricted checkpoint deserialization. + +These tests verify that persisted checkpoint loading uses a restricted +unpickler by default: +- Arbitrary callables are blocked during deserialization +- __reduce__ payloads cannot execute code during deserialization +- FileCheckpointStorage accepts allowed_checkpoint_types for extension +- User-defined types are blocked unless explicitly allowed +- Built-in safe types and framework types are always allowed +""" + +import base64 +import os +import pickle +import tempfile +from dataclasses import dataclass +from datetime import datetime, timezone + +import pytest + +from agent_framework import WorkflowCheckpointException +from agent_framework._workflows._checkpoint import FileCheckpointStorage +from agent_framework._workflows._checkpoint_encoding import ( + _BUILTIN_ALLOWED_TYPE_KEYS, + _PICKLE_MARKER, + _TYPE_MARKER, + decode_checkpoint_value, + encode_checkpoint_value, +) + + +class MaliciousPayload: + """A class whose __reduce__ executes code during unpickling.""" + + def __reduce__(self): + return (os.getpid, ()) + + +def test_restricted_decode_blocks_arbitrary_callable(): + """Restricted decoding blocks arbitrary module-level callables.""" + pickled = pickle.dumps(os.getpid, protocol=pickle.HIGHEST_PROTOCOL) + encoded_b64 = base64.b64encode(pickled).decode("ascii") + + checkpoint_value = { + _PICKLE_MARKER: encoded_b64, + _TYPE_MARKER: "builtins:builtin_function_or_method", + } + + with pytest.raises(WorkflowCheckpointException, match="deserialization blocked"): + decode_checkpoint_value(checkpoint_value, allowed_types=frozenset()) + + +def test_restricted_decode_blocks_reduce_payload(): + """__reduce__-based payloads are blocked before code can execute.""" + payload = MaliciousPayload() + pickled = pickle.dumps(payload, protocol=pickle.HIGHEST_PROTOCOL) + encoded_b64 = base64.b64encode(pickled).decode("ascii") + + checkpoint_value = { + _PICKLE_MARKER: encoded_b64, + _TYPE_MARKER: f"{MaliciousPayload.__module__}:{MaliciousPayload.__qualname__}", + } + + with pytest.raises(WorkflowCheckpointException, match="deserialization blocked"): + decode_checkpoint_value(checkpoint_value, allowed_types=frozenset()) + + +def test_restricted_decode_prevents_code_execution(): + """Restricted deserialization prevents __reduce__ code from running.""" + marker_file = tempfile.mktemp(suffix="_checkpoint_test_marker") + + payload_bytes = pickle.dumps( + type("Exploit", (), {"__reduce__": lambda self: (eval, (f"open({marker_file!r}, 'w').write('pwned')",))})(), + protocol=pickle.HIGHEST_PROTOCOL, + ) + encoded_b64 = base64.b64encode(payload_bytes).decode("ascii") + + checkpoint_value = { + _PICKLE_MARKER: encoded_b64, + _TYPE_MARKER: "builtins:int", + } + + try: + decode_checkpoint_value(checkpoint_value, allowed_types=frozenset()) + except Exception: + pass + + assert not os.path.exists(marker_file), ( + "Restricted unpickler should have prevented code execution, but the marker file was created." + ) + + +def test_file_checkpoint_storage_accepts_allowed_types(): + """FileCheckpointStorage.__init__ accepts allowed_checkpoint_types.""" + with tempfile.TemporaryDirectory() as tmpdir: + storage = FileCheckpointStorage( + tmpdir, + allowed_checkpoint_types=["some.module:SomeType"], + ) + assert storage is not None + + +@dataclass +class _AllowedTestState: + """Test dataclass that will be explicitly allowed.""" + + name: str + value: int + + +def test_restricted_decode_blocks_unlisted_user_type(): + """User-defined types are blocked when not in allowed_checkpoint_types.""" + original = _AllowedTestState(name="test", value=42) + encoded = encode_checkpoint_value(original) + + with pytest.raises(WorkflowCheckpointException, match="deserialization blocked"): + decode_checkpoint_value(encoded, allowed_types=frozenset()) + + +def test_restricted_decode_allows_listed_user_type(): + """User-defined types are allowed when listed in allowed_types.""" + original = _AllowedTestState(name="test", value=42) + encoded = encode_checkpoint_value(original) + + type_key = f"{_AllowedTestState.__module__}:{_AllowedTestState.__qualname__}" + decoded = decode_checkpoint_value(encoded, allowed_types=frozenset({type_key})) + + assert isinstance(decoded, _AllowedTestState) + assert decoded.name == "test" + assert decoded.value == 42 + + +def test_restricted_decode_allows_builtin_safe_types(): + """Built-in safe types (datetime, set, etc.) are always allowed.""" + test_values = [ + datetime(2025, 1, 1, tzinfo=timezone.utc), + {1, 2, 3}, + frozenset({4, 5, 6}), + (1, "two", 3.0), + complex(1, 2), + ] + for original in test_values: + encoded = encode_checkpoint_value(original) + decoded = decode_checkpoint_value(encoded, allowed_types=frozenset()) + assert decoded == original + + +def test_unrestricted_decode_allows_arbitrary_types(): + """Without allowed_types, decode_checkpoint_value remains unrestricted.""" + original = _AllowedTestState(name="test", value=42) + encoded = encode_checkpoint_value(original) + + decoded = decode_checkpoint_value(encoded) + + assert isinstance(decoded, _AllowedTestState) + assert decoded.name == "test" + + +async def test_file_storage_blocks_unlisted_user_type(): + """FileCheckpointStorage blocks user types not in allowed_checkpoint_types.""" + from agent_framework import WorkflowCheckpoint + + with tempfile.TemporaryDirectory() as tmpdir: + # Save with a storage that allows the type + type_key = f"{_AllowedTestState.__module__}:{_AllowedTestState.__qualname__}" + save_storage = FileCheckpointStorage(tmpdir, allowed_checkpoint_types=[type_key]) + + checkpoint = WorkflowCheckpoint( + workflow_name="test", + graph_signature_hash="hash", + state={"data": _AllowedTestState(name="test", value=1)}, + ) + await save_storage.save(checkpoint) + + # Load with a storage that does NOT allow the type + load_storage = FileCheckpointStorage(tmpdir) + with pytest.raises(WorkflowCheckpointException, match="deserialization blocked"): + await load_storage.load(checkpoint.checkpoint_id) + + +async def test_file_storage_allows_listed_user_type(): + """FileCheckpointStorage allows user types listed in allowed_checkpoint_types.""" + from agent_framework import WorkflowCheckpoint + + with tempfile.TemporaryDirectory() as tmpdir: + type_key = f"{_AllowedTestState.__module__}:{_AllowedTestState.__qualname__}" + storage = FileCheckpointStorage(tmpdir, allowed_checkpoint_types=[type_key]) + + checkpoint = WorkflowCheckpoint( + workflow_name="test", + graph_signature_hash="hash", + state={"data": _AllowedTestState(name="allowed", value=99)}, + ) + await storage.save(checkpoint) + loaded = await storage.load(checkpoint.checkpoint_id) + + assert isinstance(loaded.state["data"], _AllowedTestState) + assert loaded.state["data"].name == "allowed" + assert loaded.state["data"].value == 99 diff --git a/python/packages/core/tests/workflow/test_request_info_event_rehydrate.py b/python/packages/core/tests/workflow/test_request_info_event_rehydrate.py index 9400084692..46969cd285 100644 --- a/python/packages/core/tests/workflow/test_request_info_event_rehydrate.py +++ b/python/packages/core/tests/workflow/test_request_info_event_rehydrate.py @@ -130,7 +130,17 @@ async def test_checkpoint_with_pending_request_info_events(): with tempfile.TemporaryDirectory() as temp_dir: # Use file-based storage to test full serialization - storage = FileCheckpointStorage(temp_dir) + storage = FileCheckpointStorage( + temp_dir, + allowed_checkpoint_types=[ + "tests.workflow.test_request_info_and_response:UserApprovalRequest", + "tests.workflow.test_request_info_and_response:CalculationRequest", + "tests.workflow.test_request_info_event_rehydrate:MockRequest", + "tests.workflow.test_request_info_event_rehydrate:SimpleApproval", + "tests.workflow.test_request_info_event_rehydrate:SlottedApproval", + "tests.workflow.test_request_info_event_rehydrate:TimedApproval", + ], + ) # Create workflow with checkpointing enabled executor = ApprovalRequiredExecutor(id="approval_executor") @@ -225,7 +235,17 @@ async def test_checkpoint_restore_with_responses_does_not_reemit_handled_request with tempfile.TemporaryDirectory() as temp_dir: # Use file-based storage to test full serialization - storage = FileCheckpointStorage(temp_dir) + storage = FileCheckpointStorage( + temp_dir, + allowed_checkpoint_types=[ + "tests.workflow.test_request_info_and_response:UserApprovalRequest", + "tests.workflow.test_request_info_and_response:CalculationRequest", + "tests.workflow.test_request_info_event_rehydrate:MockRequest", + "tests.workflow.test_request_info_event_rehydrate:SimpleApproval", + "tests.workflow.test_request_info_event_rehydrate:SlottedApproval", + "tests.workflow.test_request_info_event_rehydrate:TimedApproval", + ], + ) # Create workflow with checkpointing enabled executor = ApprovalRequiredExecutor(id="approval_executor") @@ -288,7 +308,17 @@ async def test_checkpoint_restore_with_partial_responses_reemits_unhandled_reque import tempfile with tempfile.TemporaryDirectory() as temp_dir: - storage = FileCheckpointStorage(temp_dir) + storage = FileCheckpointStorage( + temp_dir, + allowed_checkpoint_types=[ + "tests.workflow.test_request_info_and_response:UserApprovalRequest", + "tests.workflow.test_request_info_and_response:CalculationRequest", + "tests.workflow.test_request_info_event_rehydrate:MockRequest", + "tests.workflow.test_request_info_event_rehydrate:SimpleApproval", + "tests.workflow.test_request_info_event_rehydrate:SlottedApproval", + "tests.workflow.test_request_info_event_rehydrate:TimedApproval", + ], + ) # Create workflow with multiple requests executor = MultiRequestExecutor(id="multi_executor") From 747b1e0f523a26749c132bd21145e4acf7b1596b Mon Sep 17 00:00:00 2001 From: Copilot Date: Fri, 27 Mar 2026 05:51:35 +0000 Subject: [PATCH 02/17] fix: resolve mypy no-any-return error in checkpoint encoding Add explicit type annotation for super().find_class() return value to satisfy mypy's no-any-return check. Fixes #4894 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../agent_framework/_workflows/_checkpoint.py | 4 +-- .../_workflows/_checkpoint_encoding.py | 5 ++-- .../test_checkpoint_unrestricted_pickle.py | 7 ++--- .../test_request_info_event_rehydrate.py | 30 +++++++++---------- 4 files changed, 21 insertions(+), 25 deletions(-) diff --git a/python/packages/core/agent_framework/_workflows/_checkpoint.py b/python/packages/core/agent_framework/_workflows/_checkpoint.py index fc4ca4b159..8c9e814723 100644 --- a/python/packages/core/agent_framework/_workflows/_checkpoint.py +++ b/python/packages/core/agent_framework/_workflows/_checkpoint.py @@ -352,9 +352,7 @@ def _read() -> dict[str, Any]: from ._checkpoint_encoding import decode_checkpoint_value try: - decoded_checkpoint_dict = decode_checkpoint_value( - encoded_checkpoint, allowed_types=self._allowed_types - ) + decoded_checkpoint_dict = decode_checkpoint_value(encoded_checkpoint, allowed_types=self._allowed_types) except WorkflowCheckpointException: raise checkpoint = WorkflowCheckpoint.from_dict(decoded_checkpoint_dict) diff --git a/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py b/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py index e9b26aece0..9208179da9 100644 --- a/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py +++ b/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py @@ -79,7 +79,7 @@ }) -class _RestrictedUnpickler(pickle.Unpickler): +class _RestrictedUnpickler(pickle.Unpickler): # noqa: S301 """Unpickler that restricts which classes may be instantiated. Only classes whose ``module:qualname`` key appears in the combined allow @@ -99,7 +99,8 @@ def find_class(self, module: str, name: str) -> type: or type_key in self._allowed_types or module.startswith(_FRAMEWORK_MODULE_PREFIX) ): - return super().find_class(module, name) + result: type = super().find_class(module, name) + return result raise WorkflowCheckpointException( f"Checkpoint deserialization blocked for type '{type_key}'. " diff --git a/python/packages/core/tests/workflow/test_checkpoint_unrestricted_pickle.py b/python/packages/core/tests/workflow/test_checkpoint_unrestricted_pickle.py index e64e2ba617..14159986e4 100644 --- a/python/packages/core/tests/workflow/test_checkpoint_unrestricted_pickle.py +++ b/python/packages/core/tests/workflow/test_checkpoint_unrestricted_pickle.py @@ -12,6 +12,7 @@ """ import base64 +import contextlib import os import pickle import tempfile @@ -23,7 +24,6 @@ from agent_framework import WorkflowCheckpointException from agent_framework._workflows._checkpoint import FileCheckpointStorage from agent_framework._workflows._checkpoint_encoding import ( - _BUILTIN_ALLOWED_TYPE_KEYS, _PICKLE_MARKER, _TYPE_MARKER, decode_checkpoint_value, @@ -81,11 +81,8 @@ def test_restricted_decode_prevents_code_execution(): _PICKLE_MARKER: encoded_b64, _TYPE_MARKER: "builtins:int", } - - try: + with contextlib.suppress(Exception): decode_checkpoint_value(checkpoint_value, allowed_types=frozenset()) - except Exception: - pass assert not os.path.exists(marker_file), ( "Restricted unpickler should have prevented code execution, but the marker file was created." diff --git a/python/packages/core/tests/workflow/test_request_info_event_rehydrate.py b/python/packages/core/tests/workflow/test_request_info_event_rehydrate.py index 46969cd285..d280a788cd 100644 --- a/python/packages/core/tests/workflow/test_request_info_event_rehydrate.py +++ b/python/packages/core/tests/workflow/test_request_info_event_rehydrate.py @@ -131,16 +131,16 @@ async def test_checkpoint_with_pending_request_info_events(): with tempfile.TemporaryDirectory() as temp_dir: # Use file-based storage to test full serialization storage = FileCheckpointStorage( - temp_dir, - allowed_checkpoint_types=[ - "tests.workflow.test_request_info_and_response:UserApprovalRequest", + temp_dir, + allowed_checkpoint_types=[ + "tests.workflow.test_request_info_and_response:UserApprovalRequest", "tests.workflow.test_request_info_and_response:CalculationRequest", "tests.workflow.test_request_info_event_rehydrate:MockRequest", "tests.workflow.test_request_info_event_rehydrate:SimpleApproval", "tests.workflow.test_request_info_event_rehydrate:SlottedApproval", "tests.workflow.test_request_info_event_rehydrate:TimedApproval", - ], - ) + ], + ) # Create workflow with checkpointing enabled executor = ApprovalRequiredExecutor(id="approval_executor") @@ -236,16 +236,16 @@ async def test_checkpoint_restore_with_responses_does_not_reemit_handled_request with tempfile.TemporaryDirectory() as temp_dir: # Use file-based storage to test full serialization storage = FileCheckpointStorage( - temp_dir, - allowed_checkpoint_types=[ - "tests.workflow.test_request_info_and_response:UserApprovalRequest", + temp_dir, + allowed_checkpoint_types=[ + "tests.workflow.test_request_info_and_response:UserApprovalRequest", "tests.workflow.test_request_info_and_response:CalculationRequest", "tests.workflow.test_request_info_event_rehydrate:MockRequest", "tests.workflow.test_request_info_event_rehydrate:SimpleApproval", "tests.workflow.test_request_info_event_rehydrate:SlottedApproval", "tests.workflow.test_request_info_event_rehydrate:TimedApproval", - ], - ) + ], + ) # Create workflow with checkpointing enabled executor = ApprovalRequiredExecutor(id="approval_executor") @@ -309,16 +309,16 @@ async def test_checkpoint_restore_with_partial_responses_reemits_unhandled_reque with tempfile.TemporaryDirectory() as temp_dir: storage = FileCheckpointStorage( - temp_dir, - allowed_checkpoint_types=[ - "tests.workflow.test_request_info_and_response:UserApprovalRequest", + temp_dir, + allowed_checkpoint_types=[ + "tests.workflow.test_request_info_and_response:UserApprovalRequest", "tests.workflow.test_request_info_and_response:CalculationRequest", "tests.workflow.test_request_info_event_rehydrate:MockRequest", "tests.workflow.test_request_info_event_rehydrate:SimpleApproval", "tests.workflow.test_request_info_event_rehydrate:SlottedApproval", "tests.workflow.test_request_info_event_rehydrate:TimedApproval", - ], - ) + ], + ) # Create workflow with multiple requests executor = MultiRequestExecutor(id="multi_executor") From 204b117fc1d2c83a4517dda5e2fc0834131f9e3e Mon Sep 17 00:00:00 2001 From: Copilot Date: Fri, 27 Mar 2026 06:04:47 +0000 Subject: [PATCH 03/17] Simplify find_class return in _RestrictedUnpickler (#4894) Remove unnecessary intermediate variable and apply # noqa: S301 # nosec directly on the super().find_class() call, matching the established pattern used on the pickle.loads() call in the same file. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../core/agent_framework/_workflows/_checkpoint_encoding.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py b/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py index 9208179da9..b36a101ec6 100644 --- a/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py +++ b/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py @@ -99,8 +99,7 @@ def find_class(self, module: str, name: str) -> type: or type_key in self._allowed_types or module.startswith(_FRAMEWORK_MODULE_PREFIX) ): - result: type = super().find_class(module, name) - return result + return super().find_class(module, name) # noqa: S301 # nosec raise WorkflowCheckpointException( f"Checkpoint deserialization blocked for type '{type_key}'. " From 3e40ec41e19997bf921a0000555ec7c8b3da6d23 Mon Sep 17 00:00:00 2001 From: Copilot Date: Fri, 27 Mar 2026 06:08:20 +0000 Subject: [PATCH 04/17] Address review feedback for #4894: Python: Harden Python checkpoint persistence defaults --- .../core/agent_framework/_workflows/_checkpoint_encoding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py b/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py index b36a101ec6..1a8f8bc108 100644 --- a/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py +++ b/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py @@ -99,7 +99,7 @@ def find_class(self, module: str, name: str) -> type: or type_key in self._allowed_types or module.startswith(_FRAMEWORK_MODULE_PREFIX) ): - return super().find_class(module, name) # noqa: S301 # nosec + return super().find_class(module, name) # type: ignore[no-any-return] # nosec raise WorkflowCheckpointException( f"Checkpoint deserialization blocked for type '{type_key}'. " From 0a6a1691ac7b8a38ca24c740a137313cb19326af Mon Sep 17 00:00:00 2001 From: Copilot Date: Fri, 27 Mar 2026 06:11:44 +0000 Subject: [PATCH 05/17] Restore # noqa: S301 on line 102 of _checkpoint_encoding.py (#4894) The review feedback correctly identified that removing the # noqa: S301 suppression from the find_class return statement would cause a ruff S301 lint failure, since the project enables bandit ("S") rules. This restores consistency with lines 82 and 246 in the same file. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../core/agent_framework/_workflows/_checkpoint_encoding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py b/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py index 1a8f8bc108..a241002c3f 100644 --- a/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py +++ b/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py @@ -99,7 +99,7 @@ def find_class(self, module: str, name: str) -> type: or type_key in self._allowed_types or module.startswith(_FRAMEWORK_MODULE_PREFIX) ): - return super().find_class(module, name) # type: ignore[no-any-return] # nosec + return super().find_class(module, name) # type: ignore[no-any-return] # nosec # noqa: S301 raise WorkflowCheckpointException( f"Checkpoint deserialization blocked for type '{type_key}'. " From 6f9bb5882e938ef48b6a384f78b855e189e72078 Mon Sep 17 00:00:00 2001 From: Copilot Date: Fri, 27 Mar 2026 06:13:33 +0000 Subject: [PATCH 06/17] Address review feedback for #4894: Python: Harden Python checkpoint persistence defaults --- .../core/agent_framework/_workflows/_checkpoint_encoding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py b/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py index a241002c3f..1a8f8bc108 100644 --- a/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py +++ b/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py @@ -99,7 +99,7 @@ def find_class(self, module: str, name: str) -> type: or type_key in self._allowed_types or module.startswith(_FRAMEWORK_MODULE_PREFIX) ): - return super().find_class(module, name) # type: ignore[no-any-return] # nosec # noqa: S301 + return super().find_class(module, name) # type: ignore[no-any-return] # nosec raise WorkflowCheckpointException( f"Checkpoint deserialization blocked for type '{type_key}'. " From 8e4da79ad60b4a310a6f9261a2081eca4292e9b8 Mon Sep 17 00:00:00 2001 From: Copilot Date: Fri, 27 Mar 2026 07:08:51 +0000 Subject: [PATCH 07/17] Address PR review comments on checkpoint encoding (#4894) - Move module docstring to proper position after __future__ import - Fix find_class return type annotation to type[Any] - Add missing # noqa: S301 pragma on find_class return - Improve error message to reference both allowed_types param and FileCheckpointStorage.allowed_checkpoint_types - Add -> None return annotation to FileCheckpointStorage.__init__ - Replace tempfile.mktemp with TemporaryDirectory in test - Replace contextlib.suppress with pytest.raises for precise assertion - Remove unused contextlib import Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../agent_framework/_workflows/_checkpoint.py | 2 +- .../_workflows/_checkpoint_encoding.py | 26 +++++------ .../test_checkpoint_unrestricted_pickle.py | 43 +++++++++++-------- 3 files changed, 41 insertions(+), 30 deletions(-) diff --git a/python/packages/core/agent_framework/_workflows/_checkpoint.py b/python/packages/core/agent_framework/_workflows/_checkpoint.py index 8c9e814723..5e69a6f634 100644 --- a/python/packages/core/agent_framework/_workflows/_checkpoint.py +++ b/python/packages/core/agent_framework/_workflows/_checkpoint.py @@ -264,7 +264,7 @@ def __init__( storage_path: str | Path, *, allowed_checkpoint_types: list[str] | None = None, - ): + ) -> None: """Initialize the file storage. Args: diff --git a/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py b/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py index 1a8f8bc108..852ad91589 100644 --- a/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py +++ b/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py @@ -2,14 +2,6 @@ from __future__ import annotations -import base64 -import io -import logging -import pickle # nosec # noqa: S403 -from typing import Any - -from ..exceptions import WorkflowCheckpointException - """Checkpoint encoding using JSON structure with pickle+base64 for arbitrary data. This hybrid approach provides: @@ -25,6 +17,14 @@ ``"module:qualname"`` strings. """ +import base64 +import io +import logging +import pickle # nosec # noqa: S403 +from typing import Any + +from ..exceptions import WorkflowCheckpointException + logger = logging.getLogger("agent_framework") @@ -91,7 +91,7 @@ def __init__(self, data: bytes, allowed_types: frozenset[str]) -> None: super().__init__(io.BytesIO(data)) self._allowed_types = allowed_types - def find_class(self, module: str, name: str) -> type: + def find_class(self, module: str, name: str) -> type[Any]: type_key = f"{module}:{name}" if ( @@ -99,12 +99,14 @@ def find_class(self, module: str, name: str) -> type: or type_key in self._allowed_types or module.startswith(_FRAMEWORK_MODULE_PREFIX) ): - return super().find_class(module, name) # type: ignore[no-any-return] # nosec + return super().find_class(module, name) # type: ignore[no-any-return] # noqa: S301 # nosec raise WorkflowCheckpointException( f"Checkpoint deserialization blocked for type '{type_key}'. " - f"To allow this type, add it to 'allowed_checkpoint_types' on " - f"your checkpoint storage." + f"To allow this type, either include its 'module:qualname' key in the " + f"'allowed_types' set passed to 'decode_checkpoint_value', or add it to " + f"'allowed_checkpoint_types' on your checkpoint storage " + f"(for example, 'FileCheckpointStorage.allowed_checkpoint_types')." ) diff --git a/python/packages/core/tests/workflow/test_checkpoint_unrestricted_pickle.py b/python/packages/core/tests/workflow/test_checkpoint_unrestricted_pickle.py index 14159986e4..3480d2d6ce 100644 --- a/python/packages/core/tests/workflow/test_checkpoint_unrestricted_pickle.py +++ b/python/packages/core/tests/workflow/test_checkpoint_unrestricted_pickle.py @@ -12,7 +12,6 @@ """ import base64 -import contextlib import os import pickle import tempfile @@ -69,24 +68,34 @@ def test_restricted_decode_blocks_reduce_payload(): def test_restricted_decode_prevents_code_execution(): """Restricted deserialization prevents __reduce__ code from running.""" - marker_file = tempfile.mktemp(suffix="_checkpoint_test_marker") - - payload_bytes = pickle.dumps( - type("Exploit", (), {"__reduce__": lambda self: (eval, (f"open({marker_file!r}, 'w').write('pwned')",))})(), - protocol=pickle.HIGHEST_PROTOCOL, - ) - encoded_b64 = base64.b64encode(payload_bytes).decode("ascii") + with tempfile.TemporaryDirectory() as tmpdir: + marker_file = os.path.join(tmpdir, "checkpoint_test_marker") + + payload_bytes = pickle.dumps( + type( + "Exploit", + (), + { + "__reduce__": lambda self: ( + eval, + (f"open({marker_file!r}, 'w').write('pwned')",), + ) + }, + )(), + protocol=pickle.HIGHEST_PROTOCOL, + ) + encoded_b64 = base64.b64encode(payload_bytes).decode("ascii") - checkpoint_value = { - _PICKLE_MARKER: encoded_b64, - _TYPE_MARKER: "builtins:int", - } - with contextlib.suppress(Exception): - decode_checkpoint_value(checkpoint_value, allowed_types=frozenset()) + checkpoint_value = { + _PICKLE_MARKER: encoded_b64, + _TYPE_MARKER: "builtins:int", + } + with pytest.raises(WorkflowCheckpointException, match="deserialization blocked"): + decode_checkpoint_value(checkpoint_value, allowed_types=frozenset()) - assert not os.path.exists(marker_file), ( - "Restricted unpickler should have prevented code execution, but the marker file was created." - ) + assert not os.path.exists(marker_file), ( + "Restricted unpickler should have prevented code execution, but the marker file was created." + ) def test_file_checkpoint_storage_accepts_allowed_types(): From dfc8579d45760f28bddb346cbb79fd53605adf0d Mon Sep 17 00:00:00 2001 From: Copilot Date: Fri, 27 Mar 2026 07:12:49 +0000 Subject: [PATCH 08/17] Address PR #4941 review comments: fix docstring position and return type - Move module docstring before 'from __future__' import so it populates __doc__ (comment #4) - Change find_class return annotation from type[Any] to type to avoid misleading callers about non-type returns like copyreg._reconstructor (comment #2) Comments #1, #3, #5, #6, #7, #8 were already addressed in the current code. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../core/agent_framework/_workflows/_checkpoint_encoding.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py b/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py index 852ad91589..90663e9547 100644 --- a/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py +++ b/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py @@ -1,7 +1,5 @@ # Copyright (c) Microsoft. All rights reserved. -from __future__ import annotations - """Checkpoint encoding using JSON structure with pickle+base64 for arbitrary data. This hybrid approach provides: @@ -17,6 +15,8 @@ ``"module:qualname"`` strings. """ +from __future__ import annotations + import base64 import io import logging @@ -91,7 +91,7 @@ def __init__(self, data: bytes, allowed_types: frozenset[str]) -> None: super().__init__(io.BytesIO(data)) self._allowed_types = allowed_types - def find_class(self, module: str, name: str) -> type[Any]: + def find_class(self, module: str, name: str) -> type: type_key = f"{module}:{name}" if ( From 96afb37f06e89928bf6ffcfc2ea6af6ee6f178fe Mon Sep 17 00:00:00 2001 From: Copilot Date: Fri, 27 Mar 2026 07:14:35 +0000 Subject: [PATCH 09/17] Address review feedback for #4894: review comment fixes --- .../core/agent_framework/_workflows/_checkpoint_encoding.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py b/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py index 90663e9547..4d07da95c0 100644 --- a/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py +++ b/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py @@ -25,7 +25,6 @@ from ..exceptions import WorkflowCheckpointException - logger = logging.getLogger("agent_framework") # Marker to identify pickled values in serialized JSON @@ -99,7 +98,7 @@ def find_class(self, module: str, name: str) -> type: or type_key in self._allowed_types or module.startswith(_FRAMEWORK_MODULE_PREFIX) ): - return super().find_class(module, name) # type: ignore[no-any-return] # noqa: S301 # nosec + return super().find_class(module, name) # type: ignore[no-any-return] # nosec raise WorkflowCheckpointException( f"Checkpoint deserialization blocked for type '{type_key}'. " From ad2a9f61faeea3c235d787090b5e131d02e0bdeb Mon Sep 17 00:00:00 2001 From: Copilot Date: Wed, 8 Apr 2026 05:35:32 +0000 Subject: [PATCH 10/17] fix: use pickle.UnpicklingError in RestrictedUnpickler and improve docstring (#4894) - Change _RestrictedUnpickler.find_class to raise pickle.UnpicklingError instead of WorkflowCheckpointException, since it is pickle-level concern that gets wrapped by the caller in _base64_to_unpickle. - Remove now-unnecessary WorkflowCheckpointException re-raise in _base64_to_unpickle (pickle.UnpicklingError is caught by the generic except Exception handler and wrapped). - Expand decode_checkpoint_value docstring to show a concrete example of the module:qualname format with a user-defined class. - Add regression test verifying find_class raises pickle.UnpicklingError. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../_workflows/_checkpoint_encoding.py | 20 ++++++++++++------- .../test_checkpoint_unrestricted_pickle.py | 11 ++++++++++ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py b/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py index 4d07da95c0..5d08d19681 100644 --- a/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py +++ b/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py @@ -83,7 +83,7 @@ class _RestrictedUnpickler(pickle.Unpickler): # noqa: S301 Only classes whose ``module:qualname`` key appears in the combined allow set (built-in safe types + framework types + caller-specified extras) are - permitted. All other classes raise :class:`WorkflowCheckpointException`. + permitted. All other classes raise :class:`pickle.UnpicklingError`. """ def __init__(self, data: bytes, allowed_types: frozenset[str]) -> None: @@ -100,7 +100,7 @@ def find_class(self, module: str, name: str) -> type: ): return super().find_class(module, name) # type: ignore[no-any-return] # nosec - raise WorkflowCheckpointException( + raise pickle.UnpicklingError( f"Checkpoint deserialization blocked for type '{type_key}'. " f"To allow this type, either include its 'module:qualname' key in the " f"'allowed_types' set passed to 'decode_checkpoint_value', or add it to " @@ -136,9 +136,17 @@ def decode_checkpoint_value(value: Any, *, allowed_types: frozenset[str] | None value: A JSON-deserialized value from checkpoint storage. allowed_types: If not ``None``, restrict pickle deserialization to the built-in safe set, framework types, and the types listed here. - Each entry should use ``"module:qualname"`` format (e.g., - ``"my_app.models:SafeState"``). If ``None``, no restriction is - applied (backward-compatible behavior). + Each entry should use ``"module:qualname"`` format — that is, the + dotted module path followed by a colon and the class + ``__qualname__``. For example, given a user-defined class:: + + # my_app/models.py + class MyState: + ... + + the corresponding entry would be ``"my_app.models:MyState"``. + If ``None``, no restriction is applied (backward-compatible + behavior). Returns: The original Python value. @@ -245,8 +253,6 @@ def _base64_to_unpickle(encoded: str, *, allowed_types: frozenset[str] | None = if allowed_types is not None: return _RestrictedUnpickler(pickled, allowed_types).load() return pickle.loads(pickled) # nosec # noqa: S301 - except WorkflowCheckpointException: - raise except Exception as exc: raise WorkflowCheckpointException(f"Failed to decode pickled checkpoint data: {exc}") from exc diff --git a/python/packages/core/tests/workflow/test_checkpoint_unrestricted_pickle.py b/python/packages/core/tests/workflow/test_checkpoint_unrestricted_pickle.py index 3480d2d6ce..c70d8c85c3 100644 --- a/python/packages/core/tests/workflow/test_checkpoint_unrestricted_pickle.py +++ b/python/packages/core/tests/workflow/test_checkpoint_unrestricted_pickle.py @@ -205,3 +205,14 @@ async def test_file_storage_allows_listed_user_type(): assert isinstance(loaded.state["data"], _AllowedTestState) assert loaded.state["data"].name == "allowed" assert loaded.state["data"].value == 99 + + +def test_restricted_unpickler_raises_pickle_error(): + """_RestrictedUnpickler.find_class raises pickle.UnpicklingError, not a framework exception.""" + from agent_framework._workflows._checkpoint_encoding import _RestrictedUnpickler + + pickled = pickle.dumps(os.getpid, protocol=pickle.HIGHEST_PROTOCOL) + + unpickler = _RestrictedUnpickler(pickled, frozenset()) + with pytest.raises(pickle.UnpicklingError, match="deserialization blocked"): + unpickler.load() From f0a990b9dc1c339a1e3804e1c9d48e49ca829670 Mon Sep 17 00:00:00 2001 From: Copilot Date: Wed, 8 Apr 2026 05:39:54 +0000 Subject: [PATCH 11/17] fix: address PR #4941 review comments for checkpoint encoding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Comment 1 (line 103): Already resolved in prior commit — _RestrictedUnpickler now raises pickle.UnpicklingError instead of WorkflowCheckpointException. - Comment 2 (line 140): Add concrete usage examples to decode_checkpoint_value docstring showing both direct allowed_types usage and FileCheckpointStorage allowed_checkpoint_types usage. Rename 'SafeState' to 'MyState' across all docstrings for consistency, making it clear this is a user-defined class name. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../agent_framework/_workflows/_checkpoint.py | 4 ++-- .../_workflows/_checkpoint_encoding.py | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/python/packages/core/agent_framework/_workflows/_checkpoint.py b/python/packages/core/agent_framework/_workflows/_checkpoint.py index 5e69a6f634..f9a940a7db 100644 --- a/python/packages/core/agent_framework/_workflows/_checkpoint.py +++ b/python/packages/core/agent_framework/_workflows/_checkpoint.py @@ -254,7 +254,7 @@ class FileCheckpointStorage: storage = FileCheckpointStorage( "/tmp/checkpoints", allowed_checkpoint_types=[ - "my_app.models:SafeState", + "my_app.models:MyState", ], ) """ @@ -272,7 +272,7 @@ def __init__( allowed_checkpoint_types: Additional types (beyond the built-in safe set and framework types) that are permitted during checkpoint deserialization. Each entry should be a ``"module:qualname"`` - string (e.g., ``"my_app.models:SafeState"``). + string (e.g., ``"my_app.models:MyState"``). """ self.storage_path = Path(storage_path) self.storage_path.mkdir(parents=True, exist_ok=True) diff --git a/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py b/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py index 5d08d19681..83a1561d6f 100644 --- a/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py +++ b/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py @@ -144,7 +144,21 @@ def decode_checkpoint_value(value: Any, *, allowed_types: frozenset[str] | None class MyState: ... - the corresponding entry would be ``"my_app.models:MyState"``. + the corresponding entry would be ``"my_app.models:MyState"``:: + + decode_checkpoint_value( + data, + allowed_types=frozenset({"my_app.models:MyState"}), + ) + + When using :class:`FileCheckpointStorage`, pass the same strings + via ``allowed_checkpoint_types``:: + + storage = FileCheckpointStorage( + "/tmp/checkpoints", + allowed_checkpoint_types=["my_app.models:MyState"], + ) + If ``None``, no restriction is applied (backward-compatible behavior). From 363a09684cb2c87c6f61f76b12ec2cba9d264cad Mon Sep 17 00:00:00 2001 From: Copilot Date: Wed, 8 Apr 2026 05:43:01 +0000 Subject: [PATCH 12/17] fix: replace deprecated 'builtin' repo with pre-commit-hooks in pre-commit config pre-commit 4.x no longer supports 'repo: builtin'. Merge those hooks into the existing pre-commit-hooks repo entry. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- python/.pre-commit-config.yaml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/python/.pre-commit-config.yaml b/python/.pre-commit-config.yaml index bbb2683c5c..adf7e6e5b3 100644 --- a/python/.pre-commit-config.yaml +++ b/python/.pre-commit-config.yaml @@ -1,7 +1,8 @@ fail_fast: true exclude: ^scripts/ repos: - - repo: builtin + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 hooks: - id: check-toml name: Check TOML files @@ -34,9 +35,6 @@ repos: - id: no-commit-to-branch name: Protect main branch args: [--branch, main] - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v6.0.0 - hooks: - id: check-ast name: Check Valid Python Samples types: ["python"] From 889ad10e59f7a30468817e19ee2f15ec3dabfc0e Mon Sep 17 00:00:00 2001 From: Copilot Date: Wed, 8 Apr 2026 05:43:13 +0000 Subject: [PATCH 13/17] style: apply pyupgrade formatting to docstring example Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../core/agent_framework/_workflows/_checkpoint_encoding.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py b/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py index 83a1561d6f..927b9ea0d4 100644 --- a/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py +++ b/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py @@ -141,8 +141,7 @@ def decode_checkpoint_value(value: Any, *, allowed_types: frozenset[str] | None ``__qualname__``. For example, given a user-defined class:: # my_app/models.py - class MyState: - ... + class MyState: ... the corresponding entry would be ``"my_app.models:MyState"``:: From 5689638e3953875d5d1f5c66be7505dd22fae037 Mon Sep 17 00:00:00 2001 From: Copilot Date: Wed, 8 Apr 2026 05:51:16 +0000 Subject: [PATCH 14/17] fix: resolve pre-commit hook paths for monorepo git root The poe-check and bandit hooks referenced paths relative to python/ but pre-commit runs hooks from the git root (monorepo root). Fix poe-check entry to cd into python/ first, and update bandit config path to python/pyproject.toml. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- python/.pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/.pre-commit-config.yaml b/python/.pre-commit-config.yaml index adf7e6e5b3..34380f374a 100644 --- a/python/.pre-commit-config.yaml +++ b/python/.pre-commit-config.yaml @@ -50,14 +50,14 @@ repos: hooks: - id: poe-check name: Run checks through Poe - entry: uv run python scripts/workspace_poe_tasks.py prek-check + entry: bash -c 'cd python && uv run python scripts/workspace_poe_tasks.py prek-check -- "$@"' -- language: system - repo: https://github.com/PyCQA/bandit rev: 1.9.4 hooks: - id: bandit name: Bandit Security Checks - args: ["-c", "pyproject.toml"] + args: ["-c", "python/pyproject.toml"] additional_dependencies: ["bandit[toml]"] - repo: https://github.com/astral-sh/uv-pre-commit # uv version. From 151ab6b413aedea912276a3d519959bf32f79a08 Mon Sep 17 00:00:00 2001 From: Copilot Date: Thu, 9 Apr 2026 09:40:28 +0000 Subject: [PATCH 15/17] Fix pre-commit config paths for prek --cd python execution Revert bandit config path from 'python/pyproject.toml' to 'pyproject.toml' and poe-check entry from explicit 'cd python' wrapper to direct invocation, since prek --cd python already sets the working directory to python/. Also apply ruff formatting fixes to cosmos checkpoint storage files. Fixes #4894 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- python/.pre-commit-config.yaml | 4 ++-- .../_checkpoint_storage.py | 10 ++-------- .../tests/test_cosmos_checkpoint_storage.py | 4 +--- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/python/.pre-commit-config.yaml b/python/.pre-commit-config.yaml index 34380f374a..adf7e6e5b3 100644 --- a/python/.pre-commit-config.yaml +++ b/python/.pre-commit-config.yaml @@ -50,14 +50,14 @@ repos: hooks: - id: poe-check name: Run checks through Poe - entry: bash -c 'cd python && uv run python scripts/workspace_poe_tasks.py prek-check -- "$@"' -- + entry: uv run python scripts/workspace_poe_tasks.py prek-check language: system - repo: https://github.com/PyCQA/bandit rev: 1.9.4 hooks: - id: bandit name: Bandit Security Checks - args: ["-c", "python/pyproject.toml"] + args: ["-c", "pyproject.toml"] additional_dependencies: ["bandit[toml]"] - repo: https://github.com/astral-sh/uv-pre-commit # uv version. diff --git a/python/packages/azure-cosmos/agent_framework_azure_cosmos/_checkpoint_storage.py b/python/packages/azure-cosmos/agent_framework_azure_cosmos/_checkpoint_storage.py index 4544311fd9..1b6257f203 100644 --- a/python/packages/azure-cosmos/agent_framework_azure_cosmos/_checkpoint_storage.py +++ b/python/packages/azure-cosmos/agent_framework_azure_cosmos/_checkpoint_storage.py @@ -315,10 +315,7 @@ async def get_latest(self, *, workflow_name: str) -> WorkflowCheckpoint | None: """ await self._ensure_container_proxy() - query = ( - "SELECT * FROM c WHERE c.workflow_name = @workflow_name " - "ORDER BY c.timestamp DESC OFFSET 0 LIMIT 1" - ) + query = "SELECT * FROM c WHERE c.workflow_name = @workflow_name ORDER BY c.timestamp DESC OFFSET 0 LIMIT 1" parameters: list[dict[str, object]] = [ {"name": "@workflow_name", "value": workflow_name}, ] @@ -351,10 +348,7 @@ async def list_checkpoint_ids(self, *, workflow_name: str) -> list[CheckpointID] """ await self._ensure_container_proxy() - query = ( - "SELECT c.checkpoint_id FROM c WHERE c.workflow_name = @workflow_name " - "ORDER BY c.timestamp ASC" - ) + query = "SELECT c.checkpoint_id FROM c WHERE c.workflow_name = @workflow_name ORDER BY c.timestamp ASC" parameters: list[dict[str, object]] = [ {"name": "@workflow_name", "value": workflow_name}, ] diff --git a/python/packages/azure-cosmos/tests/test_cosmos_checkpoint_storage.py b/python/packages/azure-cosmos/tests/test_cosmos_checkpoint_storage.py index 5e183c3223..52155d0e21 100644 --- a/python/packages/azure-cosmos/tests/test_cosmos_checkpoint_storage.py +++ b/python/packages/azure-cosmos/tests/test_cosmos_checkpoint_storage.py @@ -402,9 +402,7 @@ async def test_list_checkpoint_ids_empty_returns_empty(mock_container: MagicMock # --- Tests for close and context manager --- -async def test_close_closes_owned_client( - monkeypatch: pytest.MonkeyPatch, mock_cosmos_client: MagicMock -) -> None: +async def test_close_closes_owned_client(monkeypatch: pytest.MonkeyPatch, mock_cosmos_client: MagicMock) -> None: mock_factory = MagicMock(return_value=mock_cosmos_client) monkeypatch.setattr(checkpoint_storage_module, "CosmosClient", mock_factory) From 160fc2bbbaa6bff3ad56c196a45f29e25d8b5148 Mon Sep 17 00:00:00 2001 From: Copilot Date: Thu, 9 Apr 2026 09:48:49 +0000 Subject: [PATCH 16/17] fix: add builtins:getattr to checkpoint deserialization allowlist Pickle uses builtins:getattr to reconstruct enum members (e.g., WorkflowMessage.type which is a MessageType enum). Without it in the allowlist, checkpoint roundtrip tests fail with WorkflowCheckpointException. Fixes #4894 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../core/agent_framework/_workflows/_checkpoint_encoding.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py b/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py index 927b9ea0d4..a25a08c66a 100644 --- a/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py +++ b/python/packages/core/agent_framework/_workflows/_checkpoint_encoding.py @@ -59,6 +59,8 @@ "builtins:dict", "builtins:tuple", "builtins:type", + # getattr is used by pickle to reconstruct enum members + "builtins:getattr", # copyreg helpers used by pickle for object reconstruction "copyreg:_reconstructor", # datetime From 83bca5206dc2a993bc4e98964d2b6b101b5441d7 Mon Sep 17 00:00:00 2001 From: Copilot Date: Thu, 9 Apr 2026 09:52:27 +0000 Subject: [PATCH 17/17] Address review feedback for #4894: review comment fixes --- python/pytest.xml | 1 + python/python-coverage.xml | 28040 ++++++++++++++++ ...story_provider_conversation_persistence.py | 4 +- .../cosmos_history_provider_sessions.py | 8 +- .../cosmos_workflow_checkpointing.py | 20 +- .../cosmos_workflow_checkpointing_foundry.py | 5 +- 6 files changed, 28055 insertions(+), 23 deletions(-) create mode 100644 python/pytest.xml create mode 100644 python/python-coverage.xml diff --git a/python/pytest.xml b/python/pytest.xml new file mode 100644 index 0000000000..13d4791f5a --- /dev/null +++ b/python/pytest.xml @@ -0,0 +1 @@ +/repos/agent-framework/.worktrees/agent/fix-4894-1/python/packages/core/tests/core/test_observability.py:592: Skipping OTLP exporter tests - optional dependency not installed by default/repos/agent-framework/.worktrees/agent/fix-4894-1/python/packages/core/tests/core/test_observability.py:609: Skipping OTLP exporter tests - optional dependency not installed by default/repos/agent-framework/.worktrees/agent/fix-4894-1/python/packages/core/tests/core/test_observability.py:626: Skipping OTLP exporter tests - optional dependency not installed by default/repos/agent-framework/.worktrees/agent/fix-4894-1/python/packages/core/tests/core/test_observability.py:645: Skipping OTLP exporter tests - optional dependency not installed by default/repos/agent-framework/.worktrees/agent/fix-4894-1/python/packages/core/tests/core/test_observability.py:663: Skipping OTLP exporter tests - optional dependency not installed by default/repos/agent-framework/.worktrees/agent/fix-4894-1/python/packages/core/tests/core/test_observability.py:681: Skipping OTLP exporter tests - optional dependency not installed by default/repos/agent-framework/.worktrees/agent/fix-4894-1/python/packages/core/tests/core/test_observability.py:704: Skipping OTLP exporter tests - optional dependency not installed by default/repos/agent-framework/.worktrees/agent/fix-4894-1/python/packages/core/tests/core/test_observability.py:776: Skipping OTLP exporter tests - optional dependency not installed by default/repos/agent-framework/.worktrees/agent/fix-4894-1/python/packages/core/tests/core/test_observability.py:790: Skipping OTLP exporter tests - optional dependency not installed by default/repos/agent-framework/.worktrees/agent/fix-4894-1/python/packages/core/tests/core/test_observability.py:804: Skipping OTLP exporter tests - optional dependency not installed by default/repos/agent-framework/.worktrees/agent/fix-4894-1/python/packages/core/tests/core/test_observability.py:823: Skipping OTLP exporter tests - optional dependency not installed by default/repos/agent-framework/.worktrees/agent/fix-4894-1/python/packages/core/tests/core/test_observability.py:839: Skipping OTLP exporter tests - optional dependency not installed by default/repos/agent-framework/.worktrees/agent/fix-4894-1/python/packages/core/tests/core/test_observability.py:861: Skipping OTLP exporter tests - optional dependency not installed by default/repos/agent-framework/.worktrees/agent/fix-4894-1/python/packages/core/tests/core/test_observability.py:888: Skipping OTLP exporter tests - optional dependency not installed by default/repos/agent-framework/.worktrees/agent/fix-4894-1/python/packages/core/tests/core/test_function_invocation_logic.py:1463: Error handling and failsafe behavior needs investigation in unified API/repos/agent-framework/.worktrees/agent/fix-4894-1/python/packages/core/tests/core/test_function_invocation_logic.py:2885: Failsafe behavior needs investigation in unified API/repos/agent-framework/.worktrees/agent/fix-4894-1/python/packages/declarative/tests/test_powerfx_yaml_compatibility.py:521: No non-English LC_NUMERIC locale available on this system/repos/agent-framework/.worktrees/agent/fix-4894-1/python/packages/core/tests/workflow/test_viz.py:160: Requires graphviz to be installed/repos/agent-framework/.worktrees/agent/fix-4894-1/python/packages/core/tests/workflow/test_viz.py:195: could not import 'graphviz': No module named 'graphviz' \ No newline at end of file diff --git a/python/python-coverage.xml b/python/python-coverage.xml new file mode 100644 index 0000000000..15d32ded01 --- /dev/null +++ b/python/python-coverage.xml @@ -0,0 +1,28040 @@ + + + + + + /repos/agent-framework/.worktrees/agent/fix-4894-1/python + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/samples/02-agents/conversations/cosmos_history_provider_conversation_persistence.py b/python/samples/02-agents/conversations/cosmos_history_provider_conversation_persistence.py index ef2b444d28..548f09a92a 100644 --- a/python/samples/02-agents/conversations/cosmos_history_provider_conversation_persistence.py +++ b/python/samples/02-agents/conversations/cosmos_history_provider_conversation_persistence.py @@ -82,9 +82,7 @@ async def main() -> None: ): session = agent.create_session() - response1 = await agent.run( - "My name is Ada. I'm building a distributed database in Rust.", session=session - ) + response1 = await agent.run("My name is Ada. I'm building a distributed database in Rust.", session=session) print("User: My name is Ada. I'm building a distributed database in Rust.") print(f"Assistant: {response1.text}\n") diff --git a/python/samples/02-agents/conversations/cosmos_history_provider_sessions.py b/python/samples/02-agents/conversations/cosmos_history_provider_sessions.py index 2d1861e503..31e8eef16c 100644 --- a/python/samples/02-agents/conversations/cosmos_history_provider_sessions.py +++ b/python/samples/02-agents/conversations/cosmos_history_provider_sessions.py @@ -82,9 +82,7 @@ async def main() -> None: alice_session = agent.create_session(session_id="tenant-alice-session-1") - response = await agent.run( - "Hi! I'm planning a trip to Italy. I love Renaissance art.", session=alice_session - ) + response = await agent.run("Hi! I'm planning a trip to Italy. I love Renaissance art.", session=alice_session) print("Alice: I'm planning a trip to Italy. I love Renaissance art.") print(f"Assistant: {response.text}\n") @@ -97,9 +95,7 @@ async def main() -> None: bob_session = agent.create_session(session_id="tenant-bob-session-1") - response = await agent.run( - "Hey! I'm learning to cook Thai food. I just made pad thai.", session=bob_session - ) + response = await agent.run("Hey! I'm learning to cook Thai food. I just made pad thai.", session=bob_session) print("Bob: I'm learning to cook Thai food. I just made pad thai.") print(f"Assistant: {response.text}\n") diff --git a/python/samples/03-workflows/checkpoint/cosmos_workflow_checkpointing.py b/python/samples/03-workflows/checkpoint/cosmos_workflow_checkpointing.py index 4726742ffc..fd4608db03 100644 --- a/python/samples/03-workflows/checkpoint/cosmos_workflow_checkpointing.py +++ b/python/samples/03-workflows/checkpoint/cosmos_workflow_checkpointing.py @@ -111,10 +111,7 @@ async def main() -> None: cosmos_key = os.getenv("AZURE_COSMOS_KEY") if not cosmos_endpoint or not cosmos_database_name or not cosmos_container_name: - print( - "Please set AZURE_COSMOS_ENDPOINT, AZURE_COSMOS_DATABASE_NAME, " - "and AZURE_COSMOS_CONTAINER_NAME." - ) + print("Please set AZURE_COSMOS_ENDPOINT, AZURE_COSMOS_DATABASE_NAME, and AZURE_COSMOS_CONTAINER_NAME.") return # Authentication: supports both managed identity/RBAC and key-based auth. @@ -131,12 +128,15 @@ async def main() -> None: else: from azure.identity.aio import DefaultAzureCredential - async with DefaultAzureCredential() as credential, CosmosCheckpointStorage( - endpoint=cosmos_endpoint, - credential=credential, - database_name=cosmos_database_name, - container_name=cosmos_container_name, - ) as checkpoint_storage: + async with ( + DefaultAzureCredential() as credential, + CosmosCheckpointStorage( + endpoint=cosmos_endpoint, + credential=credential, + database_name=cosmos_database_name, + container_name=cosmos_container_name, + ) as checkpoint_storage, + ): await _run_workflow(checkpoint_storage) diff --git a/python/samples/03-workflows/checkpoint/cosmos_workflow_checkpointing_foundry.py b/python/samples/03-workflows/checkpoint/cosmos_workflow_checkpointing_foundry.py index 49c3e779f9..7d4f6ad17f 100644 --- a/python/samples/03-workflows/checkpoint/cosmos_workflow_checkpointing_foundry.py +++ b/python/samples/03-workflows/checkpoint/cosmos_workflow_checkpointing_foundry.py @@ -57,10 +57,7 @@ async def main() -> None: return if not cosmos_endpoint or not cosmos_database_name or not cosmos_container_name: - print( - "Please set AZURE_COSMOS_ENDPOINT, AZURE_COSMOS_DATABASE_NAME, " - "and AZURE_COSMOS_CONTAINER_NAME." - ) + print("Please set AZURE_COSMOS_ENDPOINT, AZURE_COSMOS_DATABASE_NAME, and AZURE_COSMOS_CONTAINER_NAME.") return # Use a single AzureCliCredential for both Cosmos and Foundry,