Skip to content

fix(sync): JsonElement value-equality in EntityUpdate dedup + MeshNode reduce - #112

Merged
rbuergi merged 5 commits into
mainfrom
fix/jsonelement-dedup-and-reduce-null
Jun 30, 2026
Merged

rbuergi merged 5 commits into
mainfrom
fix/jsonelement-dedup-and-reduce-null

Conversation

@rbuergi

@rbuergi rbuergi commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Two JsonElement-as-object serialization defects in the sync core. Both are now fixed and pinned by a deterministic repro that fails pre-fix and passes post-fix.

FIX 1 — JsonSynchronizationStream.ToEntityUpdates dedup never fired (both overloads, lines 748 & 994)

DistinctBy(x => new { x.Id, x.Collection }) silently never deduplicated. x.Id is the return of DecodePointerSegment → JsonSerializer.Deserialize<object>(...), which yields a boxed JsonElement. JsonElement is a struct cursor (_parent/_idx) with no value equality, so the anonymous-key comparison treated every id as distinct — duplicate per-entity EntityUpdates flowed into the sync stream, contradicting the method's documented contract ("one distinct update per affected entity, deduplicated by id and collection").

Fix: key on the JsonElement's raw text — new { Id = x.Id is JsonElement je ? je.GetRawText() : x.Id?.ToString(), x.Collection }. ToString() stays stable for string/guid ids.

FIX 2 — MeshDataSource.ReduceToMeshNode patch path returned a null node (line 1155)

new(change.Value as MeshNode, …) returned null whenever change.Value is a JsonElement (the cross-hub / mirror patch path, where the update value is the JSON-serialized node, not a MeshNode), pushing a null-content ChangeItem into the mirror. The sibling PatchMeshNode already deserializes JsonElement → MeshNode correctly; this branch now does the same.

Fix: thread the hub's JsonSerializerOptions into the reducer and je.Deserialize<MeshNode>(options) when the value is a JsonElement. Narrow, mirrors PatchMeshNode.

Repros (both fail pre-fix, pass post-fix)

  • test/MeshWeaver.Data.Test/EntityUpdateDedupTest.cs — two patch ops on the same (id, collection) → pre-fix count 2, post-fix 1.
  • test/MeshWeaver.Graph.Test/ReduceToMeshNodeJsonElementTest.cs — a JsonElement-valued EntityUpdate through the reduce → pre-fix Value is null, post-fix equals the expected MeshNode.

Verification

  • Both repros: FAIL pre-fix (count 2 / null), PASS post-fix.
  • Sync-core guardrail held — FIX 2 required no revert. Full MeshWeaver.Data.Test (254/254) and MeshWeaver.Graph.Test (436/436) green under DOTNET_PROCESSOR_COUNT=2.

Sync-core change — the human should review the MeshDataSource reduce edit. Do not merge automatically.

🤖 Generated with Claude Code

…e reduce

Two JsonElement-as-object defects in the sync core:

FIX 1 — JsonSynchronizationStream.ToEntityUpdates (both overloads):
DistinctBy(x => new { x.Id, x.Collection }) never deduped. x.Id is the result
of DecodePointerSegment → JsonSerializer.Deserialize<object> → a boxed
JsonElement, whose struct (_parent/_idx cursor) has no value equality, so the
anonymous key compared every id as distinct and duplicate per-entity
EntityUpdates leaked into the stream (contradicting the documented "one distinct
update per affected entity"). Key on je.GetRawText() (stable; ToString() stays
stable for string/guid ids).

FIX 2 — MeshDataSource.ReduceToMeshNode patch path:
`change.Value as MeshNode` returned null when change.Value is a JsonElement
(the cross-hub / mirror patch path), pushing a null-content ChangeItem into the
mirror. Deserialize the JsonElement to MeshNode the same way the sibling
PatchMeshNode does (threaded the hub's JsonSerializerOptions into the reducer).

Repros: EntityUpdateDedupTest (count 2 → 1), ReduceToMeshNodeJsonElementTest
(null → node). Full Data.Test (254) + Graph.Test (436) green under
DOTNET_PROCESSOR_COUNT=2.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 29, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

1 237 tests  +1   1 237 ✅ +1   3m 10s ⏱️ -45s
   12 suites ±0       0 💤 ±0 
   12 files   ±0       0 ❌ ±0 

Results for commit 4ff3ad6. ± Comparison against base commit ea3b837.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jun 29, 2026 •

Copy link
Copy Markdown
Contributor

Test Results

   50 files  ± 0     50 suites  ±0   24m 47s ⏱️ -40s
5 288 tests  - 81  5 277 ✅  - 80  9 💤 ±0  2 ❌  - 1 
5 315 runs   - 81  5 304 ✅  - 80  9 💤 ±0  2 ❌  - 1 

For more details on these failures, see this check.

Results for commit 4ff3ad6. ± Comparison against base commit ea3b837.

This pull request removes 83 and adds 2 tests. Note that renamed tests count towards both.
MeshWeaver.Hosting.Monolith.Test.CompileActivityNoPhantomPathTest ‑ CompiledNodeType_LastCompilationActivityPath_PointsAtAnExistingActivityNode
MeshWeaver.Hosting.Monolith.Test.CompileActivityNoPhantomPathTest ‑ SubscribingAbsentCompileActivity_InTightLoop_StaysResponsive_NoStorm
MeshWeaver.Hosting.Monolith.Test.CopyModifyCopyBackTest ‑ CopyModifyCopyBack_UpdatesOnlyDeltas
MeshWeaver.Hosting.Monolith.Test.CreateOrUpdateNodeRequestTest ‑ Upsert_OnExistingTarget_PreservesIdentityFields
MeshWeaver.Hosting.Monolith.Test.CreateOrUpdateNodeRequestTest ‑ Upsert_OnExistingTarget_UpdatesViaStream_WasCreated_False
MeshWeaver.Hosting.Monolith.Test.CreateOrUpdateNodeRequestTest ‑ Upsert_OnMissingTarget_CreatesAndReports_WasCreated_True
MeshWeaver.Hosting.Monolith.Test.DeleteRedirectToExistingParentTest ‑ Redirect_goes_home_for_top_level_node
MeshWeaver.Hosting.Monolith.Test.DeleteRedirectToExistingParentTest ‑ Redirect_skips_virtual_parent_and_targets_nearest_existing_ancestor
MeshWeaver.Hosting.Monolith.Test.DeleteRedirectToExistingParentTest ‑ Redirect_targets_real_immediate_parent_when_it_exists
MeshWeaver.Hosting.Monolith.Test.DynamicGraphIntegrationTest ‑ CodeNode_ParentPathParsing_StripsTwoSegments(codePath: "ACME/Project/Source/code", expectedParent: "ACME/Project")
…
MeshWeaver.Data.Test.EntityUpdateDedupTest ‑ TwoOps_SameEntity_DedupeToOneUpdate
MeshWeaver.Graph.Test.ReduceToMeshNodeJsonElementTest ‑ PatchPath_JsonElementValue_DeserializesToMeshNode

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jun 30, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

1 916 tests  +1   1 911 ✅ +4   7m 8s ⏱️ -29s
   13 suites ±0       5 💤 ±0 
   13 files   ±0       0 ❌  - 3 

Results for commit 4ff3ad6. ± Comparison against base commit ea3b837.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jun 30, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

926 tests  ±0   924 ✅  - 1   7m 55s ⏱️ +41s
 13 suites ±0     1 💤 ±0 
 13 files   ±0     1 ❌ +1 

For more details on these failures, see this check.

Results for commit 4ff3ad6. ± Comparison against base commit ea3b837.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jun 30, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

   12 files  ± 0     12 suites  ±0   6m 33s ⏱️ -6s
1 209 tests  - 83  1 205 ✅  - 84  3 💤 ±0  1 ❌ +1 
1 236 runs   - 83  1 232 ✅  - 84  3 💤 ±0  1 ❌ +1 

For more details on these failures, see this check.

Results for commit 4ff3ad6. ± Comparison against base commit ea3b837.

This pull request removes 83 tests.
MeshWeaver.Hosting.Monolith.Test.CompileActivityNoPhantomPathTest ‑ CompiledNodeType_LastCompilationActivityPath_PointsAtAnExistingActivityNode
MeshWeaver.Hosting.Monolith.Test.CompileActivityNoPhantomPathTest ‑ SubscribingAbsentCompileActivity_InTightLoop_StaysResponsive_NoStorm
MeshWeaver.Hosting.Monolith.Test.CopyModifyCopyBackTest ‑ CopyModifyCopyBack_UpdatesOnlyDeltas
MeshWeaver.Hosting.Monolith.Test.CreateOrUpdateNodeRequestTest ‑ Upsert_OnExistingTarget_PreservesIdentityFields
MeshWeaver.Hosting.Monolith.Test.CreateOrUpdateNodeRequestTest ‑ Upsert_OnExistingTarget_UpdatesViaStream_WasCreated_False
MeshWeaver.Hosting.Monolith.Test.CreateOrUpdateNodeRequestTest ‑ Upsert_OnMissingTarget_CreatesAndReports_WasCreated_True
MeshWeaver.Hosting.Monolith.Test.DeleteRedirectToExistingParentTest ‑ Redirect_goes_home_for_top_level_node
MeshWeaver.Hosting.Monolith.Test.DeleteRedirectToExistingParentTest ‑ Redirect_skips_virtual_parent_and_targets_nearest_existing_ancestor
MeshWeaver.Hosting.Monolith.Test.DeleteRedirectToExistingParentTest ‑ Redirect_targets_real_immediate_parent_when_it_exists
MeshWeaver.Hosting.Monolith.Test.DynamicGraphIntegrationTest ‑ CodeNode_ParentPathParsing_StripsTwoSegments(codePath: "ACME/Project/Source/code", expectedParent: "ACME/Project")
…

♻️ This comment has been updated with latest results.

rbuergi and others added 3 commits June 30, 2026 07:31
#119 added src/MeshWeaver.AI/Data/Skill/provider-keys.md (an 8th built-in skill)
but left the two exact-set assertions in SkillNodeTypeTest +
SkillHarnessImportSourceTest at the prior 7, turning main red. Add provider-keys
(sorts after model) so the assertions match the shipped Data/Skill/*.md set.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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