diff --git a/harness/tests/test_corpus_normalize.py b/harness/tests/test_corpus_normalize.py index 67bc41e7..96a115e6 100644 --- a/harness/tests/test_corpus_normalize.py +++ b/harness/tests/test_corpus_normalize.py @@ -32,7 +32,7 @@ def test_normalize_utf8_bom_newlines_unicode_and_frontmatter(): assert document.content == "Café\nBody\n" assert document.title == "Café" - assert document.metadata["frontmatter"] == {"tags": ("uno", "due"), "title": "Café"} + assert document.metadata["frontmatter"] == {"tags": ["uno", "due"], "title": "Café"} assert document.metadata["source"] == {"owner": "docs"} assert document.metadata["acquisition"] == {"transport": "http"} assert document.source_uri == "https://host/guide.md" diff --git a/harness/tht/ports/evidence.py b/harness/tht/ports/evidence.py index 8cd64f89..0f736b0a 100644 --- a/harness/tht/ports/evidence.py +++ b/harness/tht/ports/evidence.py @@ -26,6 +26,26 @@ class FrozenDict(dict): update = _immutable +class FrozenList(list): + """A JSON-serializable list whose mutation operations are disabled.""" + + def _immutable(self, *args, **kwargs): + raise TypeError("frozen JSON metadata cannot be mutated") + + __delitem__ = _immutable + __iadd__ = _immutable + __imul__ = _immutable + __setitem__ = _immutable + append = _immutable + clear = _immutable + extend = _immutable + insert = _immutable + pop = _immutable + remove = _immutable + reverse = _immutable + sort = _immutable + + _CAMEL_BOUNDARY = re.compile(r"(?<=[a-z0-9])(?=[A-Z])") _SEPARATORS = re.compile(r"[^a-z0-9]+") _NAMESPACED_VALUE = re.compile(r"^[a-z][a-z0-9_-]*:[A-Za-z0-9._:-]+$") @@ -72,7 +92,7 @@ def freeze_json(value): if isinstance(value, Mapping): return FrozenDict({str(key): freeze_json(child) for key, child in value.items()}) if isinstance(value, Sequence) and not isinstance(value, (str, bytes, bytearray)): - return tuple(freeze_json(child) for child in value) + return FrozenList(freeze_json(child) for child in value) return value