From 7fabf0c046d72c6622a322983bef3476ec7d9703 Mon Sep 17 00:00:00 2001 From: Sampoorn Nagpal Date: Sat, 5 Sep 2026 18:39:41 +0530 Subject: [PATCH 1/2] =?UTF-8?q?fix(memory):=20validate=20merged=20configur?= =?UTF-8?q?ation=20=F0=9F=A4=96=F0=9F=A4=96=F0=9F=A4=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Sampoorn Nagpal --- .../nooa-memory/src/nooa_memory/config.py | 4 +-- .../tests/memory/test_memory_config.py | 33 +++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 packages/nooa-memory/tests/memory/test_memory_config.py diff --git a/packages/nooa-memory/src/nooa_memory/config.py b/packages/nooa-memory/src/nooa_memory/config.py index 7ef2d2330..1540ac7bf 100644 --- a/packages/nooa-memory/src/nooa_memory/config.py +++ b/packages/nooa-memory/src/nooa_memory/config.py @@ -198,5 +198,5 @@ def _validate_owner(cls, value: str | None) -> str | None: observability: ObservabilityConfig = ObservabilityConfig() def merge_with(self, **overrides: object) -> MemoryConfig: - """Return a copy with top-level fields overridden.""" - return self.model_copy(update=overrides) + """Return a validated copy with top-level fields overridden.""" + return type(self).model_validate({**self.model_dump(), **overrides}) diff --git a/packages/nooa-memory/tests/memory/test_memory_config.py b/packages/nooa-memory/tests/memory/test_memory_config.py new file mode 100644 index 000000000..8052cad0f --- /dev/null +++ b/packages/nooa-memory/tests/memory/test_memory_config.py @@ -0,0 +1,33 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 +"""Configuration merging obeys the same validation rules as construction.""" + +import pytest +from nooa_memory.config import EmbeddingConfig, MemoryConfig +from pydantic import ValidationError + + +def test_merge_coerces_boolean_without_mutating_original(): + original = MemoryConfig(enabled=True, path=":memory:") + merged = original.merge_with(enabled="false") + assert merged.enabled is False + assert merged.path == original.path + assert original.enabled is True + + +def test_merge_validates_nested_config_as_top_level_replacement(): + original = MemoryConfig(embedding=EmbeddingConfig(dim=64, batch_size=3)) + merged = original.merge_with(embedding={"backend": "hashing", "dim": 16}) + assert isinstance(merged.embedding, EmbeddingConfig) + assert merged.embedding.dim == 16 + assert merged.embedding.batch_size == EmbeddingConfig().batch_size + assert original.embedding.dim == 64 + assert original.embedding.batch_size == 3 + + +@pytest.mark.parametrize( + "overrides", [{"owner": "invalid_owner"}, {"embedding": {"backend": "unknown"}}] +) +def test_merge_rejects_invalid_configuration(overrides): + with pytest.raises(ValidationError): + MemoryConfig().merge_with(**overrides) From 52724997f92ee2be8fb10b77e8d463af45ae17e5 Mon Sep 17 00:00:00 2001 From: Sampoorn Nagpal Date: Sun, 6 Sep 2026 21:49:09 +0530 Subject: [PATCH 2/2] =?UTF-8?q?docs(memory):=20describe=20configuration-me?= =?UTF-8?q?rge=20validation=20cases=20=F0=9F=A4=96=F0=9F=A4=96=F0=9F=A4=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Sampoorn Nagpal --- packages/nooa-memory/tests/memory/test_memory_config.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/nooa-memory/tests/memory/test_memory_config.py b/packages/nooa-memory/tests/memory/test_memory_config.py index 8052cad0f..d10172259 100644 --- a/packages/nooa-memory/tests/memory/test_memory_config.py +++ b/packages/nooa-memory/tests/memory/test_memory_config.py @@ -8,6 +8,7 @@ def test_merge_coerces_boolean_without_mutating_original(): + """Overrides use Pydantic coercion without changing the original configuration.""" original = MemoryConfig(enabled=True, path=":memory:") merged = original.merge_with(enabled="false") assert merged.enabled is False @@ -16,6 +17,7 @@ def test_merge_coerces_boolean_without_mutating_original(): def test_merge_validates_nested_config_as_top_level_replacement(): + """Nested dictionaries become validated models using top-level replacement semantics.""" original = MemoryConfig(embedding=EmbeddingConfig(dim=64, batch_size=3)) merged = original.merge_with(embedding={"backend": "hashing", "dim": 16}) assert isinstance(merged.embedding, EmbeddingConfig) @@ -29,5 +31,6 @@ def test_merge_validates_nested_config_as_top_level_replacement(): "overrides", [{"owner": "invalid_owner"}, {"embedding": {"backend": "unknown"}}] ) def test_merge_rejects_invalid_configuration(overrides): + """Invalid owner and embedding settings fail during the merge, not at later use.""" with pytest.raises(ValidationError): MemoryConfig().merge_with(**overrides)