From 62a4dabc57c1791088fbc1273260ec544bdb8558 Mon Sep 17 00:00:00 2001 From: Thomas Howe Date: Thu, 24 Sep 2026 21:16:19 -0400 Subject: [PATCH] fix(compat): map legacy encoding "text" to "none" Some legacy producers write dialog/analysis/attachment entries with encoding "text" for a plain-string body. That is not a spec value (base64url, json, none), so the conserver stored it unchanged and spec-strict consumers reject it: vcon-mcp returns 400 "invalid encoding: text" from storage.vcon_mcp. In one production corpus 18% of vCons (47,105 of 259,525) carry it on an attachment. normalize_legacy_fields now rewrites encoding "text" to "none", which means the same thing, alongside the existing legacy field renames. Body is untouched. Co-Authored-By: Claude Opus 5.5 --- common/lib/vcon_compat.py | 5 +++++ common/tests/test_vcon_compat.py | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/common/lib/vcon_compat.py b/common/lib/vcon_compat.py index 41045af..75cdbfe 100644 --- a/common/lib/vcon_compat.py +++ b/common/lib/vcon_compat.py @@ -22,6 +22,9 @@ - attachment / dialog ``mimetype`` → ``mediatype`` - extension/critical: ``must_support`` (top-level or in entries) → ``critical`` +- dialog / analysis / attachment ``encoding: "text"`` → ``"none"``. Not a + spec value (``base64url``, ``json``, ``none``); legacy producers use it for + a plain-string body, which is exactly what ``none`` means. The function is conservative: if both the legacy and the spec field are present, the spec field wins and the legacy field is dropped. @@ -53,6 +56,8 @@ def _normalize_entry(entry: Dict[str, Any]) -> None: _rename(entry, "schema_version", "schema") _rename(entry, "mimetype", "mediatype") _rename(entry, "must_support", "critical") + if entry.get("encoding") == "text": + entry["encoding"] = "none" def _normalize_attachment(att: Dict[str, Any]) -> None: diff --git a/common/tests/test_vcon_compat.py b/common/tests/test_vcon_compat.py index 1672406..1bb2fd6 100644 --- a/common/tests/test_vcon_compat.py +++ b/common/tests/test_vcon_compat.py @@ -101,3 +101,19 @@ def test_empty_or_missing_fields_safe(): def test_returns_same_dict(): v = {"uuid": "u"} assert normalize_legacy_fields(v) is v + + +def test_legacy_text_encoding_to_none(): + v = { + "uuid": "u", + "attachments": [ + {"type": "searchable_text", "encoding": "text", "body": "2024 Impreza"}, + {"type": "tags", "encoding": "json", "body": "[]"}, + ], + "analysis": [{"type": "summary", "encoding": "text", "body": "s"}], + } + normalize_legacy_fields(v) + assert v["attachments"][0]["encoding"] == "none" + assert v["attachments"][0]["body"] == "2024 Impreza" + assert v["attachments"][1]["encoding"] == "json" + assert v["analysis"][0]["encoding"] == "none"