Skip to content

fix(vcon): keep draft-04 JSON bodies as values on write - #209

Merged
howethomas merged 2 commits into
mainfrom
thomashowe/con-1111-raw-json-bodies
Sep 25, 2026
Merged

howethomas merged 2 commits into
mainfrom
thomashowe/con-1111-raw-json-bodies

Conversation

@howethomas

Copy link
Copy Markdown
Contributor

Under vCon core draft-04 §2.3.2, when encoding is "json" the body is the JSON value itself, not a stringified copy. Adapters now emit raw values, but the conserver converted them back to strings on ingest and in the Vcon helpers, so every stored vCon and every link saw the -02 shape. This keeps raw values raw. Readers already accepted both forms and still do.

Changes

  • common/vcon.py: add_attachment / add_analysis keep a dict or list body as a value with encoding: "json". JSON validation runs only for a string body labelled json (the legacy form).
  • common/lib/vcon_redis.py: _stringify_json_body becomes _normalize_json_body_encoding. It no longer stringifies; it only sets encoding: "json" on a dict or list body that arrived with none or no encoding. Runs on ingest, on every VconRedis store, and in sync_vcon_from_storage.
  • common/lib/vcon_egress_compat.py: the legacy 0.0.1 egress shape handles both a raw and a stringified body.
  • common/lib/links/filters.py: includes matching serialises a dict or list body before the substring check.
  • post_analysis_to_slack: the frustration check and summary text tolerate raw bodies.
  • Storage: no changes needed; each backend persists the whole vCon. Postgres (JSONB), MongoDB, S3, Redis, file and vcon-mcp round-trip raw bodies, now with tests. Elasticsearch and Utopia already decoded both forms.

Behaviour change for consumers

vCons leaving the conserver (storage, webhooks, vcon-mcp) now carry raw JSON bodies where they used to carry strings. A downstream reader that assumes a string body needs to accept both forms. The legacy egress layer still produces the 0.0.1 shape for consumers that request it. Existing stored vCons keep their string bodies; no migration.

Tests

  • New round-trip tests for Postgres, MongoDB, Elasticsearch, vcon-mcp, Redis, S3 and file storage (external services mocked). A new API test POSTs a dict lawful basis body and a list tags body and reads them back as a dict and a list. Existing tests that asserted stringified bodies are updated.
  • Full suite in the repo's Docker setup: 750 passed, 17 skipped (731 on main). Outside Docker: 688 passed, 20 skipped.
  • milvus, dataverse, chatgpt_files, SCITT, sftp and spaceandtime storage were searched (no body-specific code) but not tested.

Refs CON-1111

🤖 Generated with Claude Code

howethomas and others added 2 commits September 25, 2026 18:17
draft-ietf-vcon-vcon-core-04 §2.3.2: with encoding "json" the body is the
JSON value itself (object/array), not a stringified copy. Adapters now
emit raw values, so the conserver must stop converting them back into
strings on write.

- common/vcon.py: add_attachment/add_analysis keep dict/list bodies as
  values with encoding forced to "json"; JSON validation only applies to
  str bodies claiming encoding "json" (the legacy -02 stringified shape).
- common/lib/vcon_redis.py: VconRedis._enforce_spec_on_write no longer
  stringifies dict/list bodies. It still fixes a real mismatch (dict/list
  body with encoding "none"/absent) by stamping encoding to "json" without
  touching the value.
- common/lib/vcon_egress_compat.py: the inverse (_body_to_legacy) now
  handles both shapes — a native dict/list body under encoding "json" is
  flipped straight to legacy encoding "none"; a str body is parsed first
  as before.
- common/lib/links/filters.py: is_included's includes substring match now
  also matches a dict/list body via its JSON-serialized form, since it can
  no longer assume body is always a string.
- conserver/links/post_analysis_to_slack: the customer_frustration
  includes check and the summary-body-as-Slack-text path now tolerate a
  raw dict/list body the same way.

Every other storage backend (postgres, mongo, s3, elasticsearch, utopia,
redis_storage, vcon_mcp, file) persists the whole vCon dict as JSON/BSON/
JSONB rather than treating body specially, so raw bodies already round-trip
unchanged; elasticsearch already had an isinstance(body, str) guard before
parsing. No migration: existing stored vCons keep string bodies, and every
reader (Vcon.decoded_body, egress_compat, filters, storage get()) still
accepts both shapes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…N-1111)

Updates existing tests that asserted the old stringify-on-write behavior,
and adds coverage for the new draft-ietf-vcon-vcon-core-04 §2.3.2 shape:

- common/tests/test_vcon_redis_spec_enforce.py,
  common/tests/test_vcon_egress_compat.py, common/tests/test_encoding.py,
  common/tests/lib/filters/test_is_included.py, tests/core/test_vcon.py,
  common/tests/test_api_branches.py: updated to expect dict/list bodies to
  stay native (not JSON-stringified), plus new cases for the
  encoding-mismatch fix, the legacy stringified shape staying untouched,
  and filters/is_included matching a raw dict/list body.
- common/tests/test_api.py: new end-to-end test posting a dict lawful_basis
  body and a list tags body through POST /vcon and reading them back via
  GET /vcon/{uuid} as a dict and a list.
- tests/storage/test_s3.py, tests/storage/test_postgres_body_roundtrip.py
  (new), tests/storage/test_mongo_body_roundtrip.py (new),
  tests/storage/test_elasticsearch_body_roundtrip.py (new),
  tests/storage/test_vcon_mcp_body_roundtrip.py (new),
  common/storage/file/test_file_storage.py,
  common/storage/redis_storage/test_redis_storage.py (new): each storage
  backend round-trips a raw dict/list body unchanged, with external
  services mocked.

Full suite: 750 passed, 17 skipped (was 731 passed, 17 skipped on
origin/main baseline; +19 net from new/updated tests).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@howethomas
howethomas merged commit 6aff85c into main Sep 25, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant