fix: oceanbase identity collation collapses case-variant scope_id and source_id - #1277
Draft
thunguo wants to merge 1 commit into
Draft
fix: oceanbase identity collation collapses case-variant scope_id and source_id#1277thunguo wants to merge 1 commit into
thunguo wants to merge 1 commit into
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes a MySQL/OceanBase collation mismatch where identity-like keys (e.g., scope_id, source_id) were being compared case-/accent-insensitively due to inheriting utf8mb4_general_ci, causing cross-scope leaks and false 409 source_conflict collisions. It introduces an explicit binary collation for identity string columns so MySQL/OceanBase semantics match SQLite’s byte-exact behavior.
Changes:
- Added
identity_string(length)to emitVARCHAR(... CHARACTER SET utf8mb4 COLLATE utf8mb4_bin)on MySQL/OceanBase while keepingString(...)on SQLite. - Switched shared persistence tables, Memory search-index tables, and Handoff Report tables to use
identity_stringfor identity-like columns. - Added MySQL DDL compilation assertions and an e2e regression test covering case-variant and accent-variant identity handling across SQLite and OceanBase.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
src/powercontext/builtin/persistence/tables.py |
Introduces identity_string() and applies it to shared schema identity columns to enforce binary collation on MySQL/OceanBase. |
src/powercontext/builtin/persistence/sqlite/memory_index.py |
Updates SQLite Memory vector index schema to use identity_string for key columns. |
src/powercontext/builtin/persistence/oceanbase/memory_index.py |
Updates OceanBase vector index table schema to use identity_string for key columns. |
src/powercontext/builtin/handoff_report/workspace_store.py |
Switches Workspace Binding table string columns to identity_string to ensure byte-exact identity comparisons on MySQL/OceanBase. |
src/powercontext/builtin/handoff_report/sqlite.py |
Switches Handoff Report activity tables to identity_string for identity-like string columns. |
src/powercontext/builtin/handoff_report/catalog_store.py |
Switches Handoff Report catalog tables to identity_string for identity-like string columns. |
tests/builtin/persistence/test_mysql_schema.py |
Adds DDL assertions ensuring identity columns compile with utf8mb4_bin collation. |
tests/builtin/persistence/test_memory.py |
Adds a DDL assertion for scope_id collation in the Memory schema. |
tests/e2e/test_runtime_server.py |
Adds an end-to-end regression test validating case/accent variant identity isolation on SQLite and OceanBase. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| versions = str(CreateTable(MEMORY_ENTRY_VERSIONS_TABLE).compile(dialect=dialect)) | ||
| heads = str(CreateTable(MEMORY_ENTRY_HEADS_TABLE).compile(dialect=dialect)) | ||
|
|
||
| assert "scope_id VARCHAR(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL" in versions |
Comment on lines
+34
to
+36
| def test_mysql_ddl_uses_utf8mb4_bin_for_identity_keys() -> None: | ||
| dialect = mysql.dialect() | ||
| ddl = str(CreateTable(SOURCES_TABLE).compile(dialect=dialect)) |
thunguo
marked this pull request as draft
August 19, 2026 10:19
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Which issue or RFC does this PR close?
Closes #1276.
Rationale for this change
scope_idandsource_idare opaque, byte-exact identities. Shared schema columns were declared as dialect-neutralString(...)with no collation, so MySQL/OceanBase inherited the server defaultutf8mb4_general_ci. SQLite compares the same columns with BINARY semantics, which is why the SQLite test matrix never caught this.On OceanBase that collation collapses case-variant and accent-variant keys:
scope_id="Alpha"and"alpha"are one Scope, so listing the second returns the first Scope's Memory.source_id="Turn-1"and"turn-1"are one Source, so a legitimate second capture is rejected as409 source_conflictand that turn never enters the Source journal.What changes are included in this PR?
identity_string(length)intables.py. SQLite keepsString; MySQL/OceanBase emitVARCHAR(..., charset="utf8mb4", collation="utf8mb4_bin").CHARACTER SET utf8mb4 COLLATE utf8mb4_binforscope_id,source_id, andsource_type.scope_id, asserts the other variant lists no entries, and captures bothTurn-1andturn-1.create_all(checkfirst=True)does not rewrite existing column collations, and OceanBase rejectsALTER COLUMN ... COLLATEwhen foreign keys exist. Existing MySQL/OceanBase schemas must be recreated on a new database (or by dropping and recreating the tables). Startup does not run an in-place ALTER.Are there any user-facing changes?
Public HTTP and SDK APIs are unchanged. On a fresh MySQL/OceanBase schema, identity comparison now matches SQLite and the Runtime: case-variant and accent-variant
scope_id/source_idvalues are distinct.This is a persisted-schema break for existing OceanBase/MySQL deployments. Databases created before this change keep
utf8mb4_general_cion identity columns and will still leak or reject those variants until the schema is recreated. SQLite files are unaffected.How was this change tested?
uv run pytest -q tests/builtin/persistence/test_mysql_schema.py tests/builtin/persistence/test_memory.py tests/test_handoff_report_catalog.py tests/test_handoff_report_workspace.py tests/test_handoff_report_repository.pyPOWERCONTEXT_TEST_OCEANBASE_URL=... uv run pytest -q tests/e2e/test_runtime_server.py::test_server_databases_keep_case_and_accent_variant_identities_distinct(sqlite + oceanbase)SHOW CREATE TABLE pc_sourceson a newly created database:scope_id,source_id, andsource_typearevarchar(...) COLLATE utf8mb4_binalphaafter writingAlphareturned 0 entries; capturingturn-1afterTurn-1was acceptedThe OceanBase checks used a dedicated empty database. An older schema on the same instance still showed
utf8mb4_general_cionsource_idand would still reproduce #1276.AI usage statement