Repository navigation
fix(storage): persist MeshNode.SyncBehavior in Postgres (decouple survives restart) - #119
Merged
Merged
Conversation
…vives restart) The static-repo "Not synced" decouple claim (MeshNode.SyncBehavior) had no Postgres column. PostgreSqlStorageAdapter persisted only the typed content plus a fixed metadata column set, so SyncBehavior was dropped on write and the row->node mapper defaulted every reload to Include. A partition an admin decoupled therefore silently re-coupled on the next restart, and the next static-repo import re-clobbered admin-managed content — e.g. a customer's Provider/Anthropic API key reverting to the shipped Azure Foundry default (atioz, 2026-06-29). Sqlite was unaffected: it serializes the whole node as JSON. - Add sync_behavior SMALLINT column to mesh_nodes (every provisioning path in the schema initializer) + V39 migration (ADD COLUMN IF NOT EXISTS across every existing schema: public, auth, and all partitions). - Write it in the UPSERT (mesh_nodes only — satellite tables don't carry it), surface it on the single-node/prefix reads (satellite-safe `0 AS sync_behavior` in the UNION branches), and read it via a defensive mapper (column absent -> Include) shared by the storage adapter and the cross-schema query provider. - Repro: SyncBehaviorPersistenceTests round-trips ExcludeThisAndChildren through the PG adapter (fails before this change, which mapped it back to Include). - Docs: add the /provider-keys skill for administering AI provider keys. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
Contributor
Test Results (shard 2) 12 files ± 0 12 suites ±0 5m 41s ⏱️ -13s Results for commit c16f077. ± Comparison against base commit 9e8ee46. This pull request removes 115 tests. |
Contributor
Contributor
Test Results (shard 0)1 576 tests - 654 1 576 ✅ - 649 3m 13s ⏱️ - 5m 6s Results for commit c16f077. ± Comparison against base commit 9e8ee46. This pull request removes 654 tests. |
Contributor
Test Results 46 files - 1 46 suites - 1 19m 14s ⏱️ - 4m 45s Results for commit c16f077. ± Comparison against base commit 9e8ee46. This pull request removes 769 and adds 10 tests. Note that renamed tests count towards both. |
rbuergi
added a commit
that referenced
this pull request
Jun 30, 2026
#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>
rbuergi
added a commit
that referenced
this pull request
Jun 30, 2026
SkillNodeTypeTest.BuiltInSkillProvider_ShipsStandardSkills_AsPickSkillNodes and SkillHarnessImportSourceTest.SkillStaticRepoSource_Emits_BuiltInSkills_Under- SkillPartition assert the exact set of shipped built-in skills with an ordered .Equal(...). #119 added a new built-in skill (src/MeshWeaver.AI/Data/Skill/ provider-keys.md) but didn't update these two expected lists, so both fail DETERMINISTICALLY on main (reproduced locally: actual has 8 skills incl. "provider-keys", expected 7). The shipped behaviour is correct — the tests were stale. Append "provider-keys" (alphabetical) to both expected lists. Verified: both tests pass (2/2). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jun 30, 2026
rbuergi
added a commit
that referenced
this pull request
Jun 30, 2026
…e reduce (#112) * fix(sync): JsonElement value-equality in EntityUpdate dedup + MeshNode 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> * test(ai): include /provider-keys in the built-in skill-set assertions #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> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jul 1, 2026
Closed
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.
Problem
The static-repo "Not synced" decouple claim (
MeshNode.SyncBehavior) had no Postgres column.PostgreSqlStorageAdapterpersists only the typedcontentJSONB plus a fixed set of metadata columns (state,version, …) —SyncBehaviorwas dropped on write and the row→node mapper never read it, so every reload from PG defaulted toInclude.Consequence: a partition an admin decoupled via Admin → Partition Sync (or per-node Stop Sync) silently re-coupled on the next restart/redeploy, and the next static-repo import re-clobbered admin-managed content. This is the root cause behind the client-i
Provider/AnthropicAPI key reverting to the shared Azure Foundry default (2026-06-29): the key was entered + the partition decoupled, but the decouple evaporated on the next deploy and the importer re-seeded our key.The decouple feature appeared to work because
SyncBehaviorsurvives in-memory (the liveMeshNodeStreamCachekeeps the full node) — it just never made it to storage. Sqlite was unaffected: it serializes the whole node as JSON, so the claim round-trips there. The bug is Postgres-only.Fix
Persist
SyncBehavioras amesh_nodescolumn (it mirrorsstate— a node-level metadata enum), the only table whose nodes participate in static-repo sync:sync_behavior SMALLINT NOT NULL DEFAULT 0tomesh_nodesin every provisioning path ofPostgreSqlSchemaInitializer.V39—ALTER TABLE … ADD COLUMN IF NOT EXISTSacross every existing schema (public + auth + all partitions), idempotent, one dynamic-SQL pass.0 AS sync_behaviorso UNION branches stay aligned), and read it via a defensive mapper (PgMeshNodeReader.ReadSyncBehavior— column absent ⇒Include) shared by the storage adapter and the cross-schema query provider. The defensive read means no existing SELECT that omits the column can break.The authoritative decouple read (
StaticRepoImporter.ReadClaimedRoots→GetMeshNodeStream→ the adapter's exact read) now returns the persisted claim, so a decoupled partition root durably skips import across restarts.Repro test
SyncBehaviorPersistenceTests(Testcontainers PG): round-tripsExcludeThisAndChildrenthroughPostgreSqlStorageAdapter.Write/Read. Fails before this change (mapper returnsInclude), passes after. Plus a default-Includecase.Verification
MeshWeaver.Hosting.PostgreSql.Test: 509 passed, 1 skipped, 0 failed (the change touches core read/write SELECTs, so the whole PG suite is the regression gate).Scope notes
mesh_node_historytrigger andauth-mirror trigger are intentionally left untouched (they insert explicit column lists; history/audit doesn't need the claim)./provider-keysskill (src/MeshWeaver.AI/Data/Skill/) documenting AI provider key administration — the decouple step it prescribes is now durable.🤖 Generated with Claude Code