Skip to content

FIX: Reject non-dictionary metadata before conversation updates - #3917

Open
YingzuoLiu wants to merge 2 commits into
microsoft:mainfrom
YingzuoLiu:fix/3910-metadata-root-validation
Open

YingzuoLiu wants to merge 2 commits into
microsoft:mainfrom
YingzuoLiu:fix/3910-metadata-root-validation

Conversation

@YingzuoLiu

@YingzuoLiu YingzuoLiu commented Nov 4, 2026 •

Copy link
Copy Markdown

Description

Fixes #3910.

The dedicated metadata replacement API accepts dynamically typed non-dictionary JSON roots, commits them, and returns success. Reading those messages then raises a Pydantic validation error. Reject non-dictionary roots with ValueError in the shared execution helper before dispatching a write. Generic raw-column updates and nested dictionary values retain their existing behavior.

Both built-in public entry points use this helper: the deprecated synchronous method calls it directly; the asynchronous method passes it to _run_database_operation_async. The regression suite now calls the synchronous public method directly and checks its deprecation warning, invalid-root rejection, and preservation of the stored message.

Tests and Documentation

Rerun on 2026-11-04 against current PR head 847854bff64e7873b21ee0e9d64bc79b1f14dd6a. Verified the tested source and test files match that remote commit. Baseline is main 77b88f5385311c0f89a935aae0de61eb4caf9fda, in a separate worktree with the same nine regression cases copied in.

  • python -m pytest tests/unit/memory/test_sqlite_memory.py -k metadata_replacement -q: baseline 6 failed, 3 passed; current head 9 passed. These are actual rerun results, including the new synchronous-entry case.
  • python -m pytest tests/unit/memory/test_sqlite_memory.py tests/unit/memory/test_azure_sql_memory.py -q: current head 198 passed, 1 skipped.
  • Ruff check/format, scoped ty check, and git diff --check passed on rerun. The repository's check_no_rest_roles and check_async_suffix checks passed during the original implementation validation.

Coverage includes list/string/null/integer roots, preservation of multiple pieces, empty/nonempty/nested dictionaries, unrelated and missing conversations, the deprecated synchronous entry, and file-database reopening in a separate Python process.

Reopen test control flow: the if sys.argv[2] == "write": block only inserts messages and verifies rejection. The pieces = await memory.get_message_pieces_async(...) assignment and all three following assertions are after and outside that block, inside the common try body. Both modes therefore check two messages, their original values, and their original metadata. This was rechecked in the source and Python AST; read mode does not skip validation.

Caller audit: repository-wide search plus an AST audit of calls in pyrit, tests, and doc found no caller using prompt_metadata=None to clear metadata, apart from the intentional invalid-input regression. Existing non-regression call sites pass dictionaries. Empty-dictionary replacement remains supported and tested. This does not assert anything about external callers.

Azure SQL coverage uses the repository's unit-test doubles; no live Azure SQL, external inference, full optional-dependency suite, or full CI is claimed. Local SOCKS proxy support required installing socksio; repository dependency files are unchanged. No notebooks changed; JupyText was not run.

CI at the latest check (2026-11-04): license/cla succeeded. Six workflows report action_required and await maintainer approval to run; full CI is not yet green.

AI assistance

OpenAI Codex performed source inspection, reproduction, implementation, and local validation under the account owner's authorization.

Copilot AI balanced review requested due to automatic review settings November 4, 2026 06:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues were identified for the conversation metadata validation change.

No open issues 🎉

What changed in this PR

Addresses #3910 by rejecting non-dictionary metadata before the dedicated conversation metadata replacement API writes to storage, preventing invalid metadata from breaking message retrieval.

Changes:

  • Adds validation in the shared execution helper, covering both asynchronous and deprecated synchronous entry points.
  • Preserves valid dictionaries, nested values, and generic raw-column update behavior.
  • Adds regression tests for invalid roots, unchanged message data, valid replacements, and persistence across separate-process database reopening.
File Description
tests/​unit/​memory/​test_sqlite_memory.py Adds metadata validation and database-reopening regression tests.
pyrit/​memory/​memory_interface.py Rejects non-dictionary metadata with ValueError before dispatching updates.

Review effort: Balanced

Call the public synchronous API directly and assert its deprecation warning,
rejection of an invalid root, and preservation of the stored message.

Validation: 9 metadata replacement tests passed; SQLite/Azure unit suites
198 passed, 1 skipped. Ruff check/format, scoped ty and diff check passed.

AI assistance: OpenAI Codex implemented and validated this follow-up under
the account owner's authorization.

This branch has not been deployed

No deployments
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.

BUG: Invalid metadata replacement commits successfully then breaks message retrieval

2 participants