Skip to content

fix(#2641): heal the self-typed User declaration inside the partition its instance query is pinned to - #2723

Merged
rbuergi merged 2 commits into
mainfrom
fix/2641-self-typed-user-row-unhealed
Aug 30, 2026
Merged

rbuergi merged 2 commits into
mainfrom
fix/2641-self-typed-user-row-unhealed

Conversation

@rbuergi

@rbuergi rbuergi commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

The UserIdentityCache half of #2641 — branch 3, and the mechanism

SelfTypedDeclarationDurableRepair (#2605) never considered the fossil User row on memex-cloud: it read the declaration paths through the ordinary path-routed IStorageAdapter.ReadMany, and User's first segment routes to the partition user — which the V27 migration renamed to auth (V31 dropped the stray one that reappeared). The path-routed probe therefore answered "absent" (the tolerated 42P01), the sweep wrote nothing, and — because "nothing found" had no log line — said nothing.

The row lives in the auth schema, which is exactly where every consumer finds it: UserNodeType pins the path-less nodeType:User query (verbatim UserIdentityCache.DirectoryQuery) to the Auth partition via a QueryRoutingRule. The collision the repair exists to remove is "the declaration answers its own instance query", so the row that matters is the one that query reaches — not the one the path route reaches.

Live evidence (memex-cloud, image carrying #2605)

probe result
search nodeType:User {path:"User", nodeType:"User", version:2, lastModified:2026-06-02} + the real accounts — the fossil, via the Auth pin
search nodeType:User path:User 0 rows — with a path the Auth pin is off, first-segment routing goes to user, absent
search path:User only the static registration (nodeType:"NodeType")
boot log neither retyped durable declaration row 'User' nor retyping 'User' failed

The fix (src/MeshWeaver.Graph/Security/SelfTypedDeclarationDurableRepair.cs)

The sweep now runs two lanes per candidate declaration, in sequence:

  1. the existing path-routed read;
  2. when the declaration's own instance query (nodeType:{path}) is pinned to a partition by MeshConfiguration.ResolveRoutingHints — the same resolution the query pipeline performs — a read of the same path inside that partition, through each writable IPartitionStorageProvider's partition-scoped adapter (CreateAdapterForTable, the seam the partition-storage hubs address a schema through regardless of the path's first segment). A fossil found there is retyped through the adapter that found it, so the write lands in the schema the row is in.

Also:

  • the candidate filter uses NodeTypeDeclarationSelfTypingValidator.IsNodeTypeDeclarationContent (now public) instead of a bare is NodeTypeDefinition, so the guard, the predicate and the candidate set share one definition of "declares a NodeType";
  • never silent again: every pass ends with one Information line naming the candidate paths, the pinned partitions it probed, and how many rows it read / retyped — the third outcome the issue comment enumerated is now a count of zero, not an absence.

Deterministic repro — SelfTypedDeclarationPinnedPartitionRepairTest

Models the production store with the in-memory partition stack: a wildcard catch-all plus a specific Auth provider whose path-routed adapter only answers for Auth/… (what the Postgres path router does with a first segment) and whose partition-scoped adapter exposes the whole store. The fossil is seeded into that store — reachable through the partition, invisible to the path.

  • Unfixed sweep (both src/ files reverted, rebuilt): fails — System.TimeoutException after 30 s, the auth row never leaves nodeType: "User" (the path-routed read never sees it)
  • Fixed sweep: passes — the auth row flips to NodeType = "NodeType", version 2 → 3, content preserved; no stray copy is written under the path route; the mirrored real account beside it is untouched.

Existing SelfTypedDeclarationDurableRepairTest (2), SelfTypedDeclarationRepairRetainsNothingTest (4) and NodeTypeDeclarationSelfTypingValidatorTest (9) all pass. Built MeshWeaver.Graph, MeshWeaver.Hosting.Monolith.Test, MeshWeaver.Graph.Test with -c Release -warnaserror: 0 Error(s).

Not in this PR

What's New: src/MeshWeaver.Documentation/Data/WhatsNew/2026-08-30-user-directory-fossil-healed-in-auth-partition.md (Category: Fix).

Refs #2641

… its instance query is pinned to

The startup repair (#2605) read the declaration paths through path-routed
storage only. On memex-cloud `User`'s first segment routes to the partition
`user`, which V27 renamed to `auth`, so the probe answered "absent" while the
fossil sat in the auth schema — exactly where UserNodeType pins every
path-less `nodeType:User` query, i.e. where UserIdentityCache reads it. The
sweep wrote nothing and, having no line for "nothing found", said nothing.

The sweep now resolves each candidate declaration's own instance query
through MeshConfiguration.ResolveRoutingHints and, when it is pinned to a
partition, also reads the path inside that partition via each writable
IPartitionStorageProvider's partition-scoped adapter (CreateAdapterForTable),
retyping a fossil through the adapter that found it. Candidates are filtered
with the validator's now-public IsNodeTypeDeclarationContent, and every pass
ends with an Information summary (paths, pinned partitions, rows read /
retyped) so a miss is a count of zero rather than silence.

SelfTypedDeclarationPinnedPartitionRepairTest models the production store
(specific Auth provider whose path-routed adapter declines `User`, whose
scoped adapter exposes the fossil): times out on the old sweep, passes now.

Refs #2641

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EPCWramjFYquX5SaMdeJKW

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.

Pull request overview

This PR extends the startup self-typed NodeType declaration repair to also probe and heal durable declaration fossils located in the partition that the declaration’s own instance query is routed to (not just where the declaration path routes), addressing the memex-cloud User fossil scenario described in #2641.

Changes:

  • Update SelfTypedDeclarationDurableRepair to run sequential “lanes”: the existing path-routed read plus partition-scoped reads for partitions pinned by query routing rules, and retype via the adapter that found the fossil.
  • Expose and reuse NodeTypeDeclarationSelfTypingValidator.IsNodeTypeDeclarationContent so guard/repair candidate selection share one definition.
  • Add a deterministic regression test modeling the pinned-partition-only visibility case, plus a What’s New entry.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
test/MeshWeaver.Hosting.Monolith.Test/SelfTypedDeclarationPinnedPartitionRepairTest.cs Adds a regression test that models a fossil reachable only via a partition-scoped adapter (Auth) and verifies it is retyped in-place without creating a stray path-routed copy.
src/MeshWeaver.Graph/Security/SelfTypedDeclarationDurableRepair.cs Adds pinned-partition probing lanes, shared candidate classification, and enhanced logging for repair visibility.
src/MeshWeaver.Graph/Security/NodeTypeDeclarationSelfTypingValidator.cs Makes the NodeType-declaration-content classifier public so repair candidate selection matches the validator’s definition.
src/MeshWeaver.Documentation/Data/WhatsNew/2026-08-30-user-directory-fossil-healed-in-auth-partition.md Documents the user-visible fix in the What’s New feed.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

…faulting lane ends only itself

Review finding: the end-of-sweep summary was emitted only on OnCompleted, so
a lane whose read faulted ended the sweep with a warning and no summary —
the exact silence the summary exists to end — and, because the lanes are
concatenated, skipped every lane after it. Each lane now isolates its own
read fault (logged, empty, next lane runs), mirroring the per-row tolerance,
and the summary is emitted on both completion and fault with the outcome
named.

Refs #2641

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EPCWramjFYquX5SaMdeJKW
@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

693 tests   501 ✅  4m 42s ⏱️
  5 suites  192 💤
  5 files      0 ❌

Results for commit 8a77351.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 5)

1 532 tests   1 532 ✅  6m 17s ⏱️
    8 suites      0 💤
    8 files        0 ❌

Results for commit 8a77351.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

968 tests   968 ✅  5m 58s ⏱️
  6 suites    0 💤
  6 files      0 ❌

Results for commit 8a77351.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 4)

   10 files     10 suites   5m 47s ⏱️
3 463 tests 3 463 ✅ 0 💤 0 ❌
4 102 runs  4 102 ✅ 0 💤 0 ❌

Results for commit 8a77351.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

913 tests   913 ✅  5m 29s ⏱️
  6 suites    0 💤
  6 files      0 ❌

Results for commit 8a77351.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

792 tests   788 ✅  6m 39s ⏱️
  6 suites    4 💤
  6 files      0 ❌

Results for commit 8a77351.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results

   41 files     41 suites   34m 54s ⏱️
8 361 tests 8 165 ✅ 196 💤 0 ❌
9 000 runs  8 804 ✅ 196 💤 0 ❌

Results for commit 8a77351.

@rbuergi
rbuergi merged commit d3272b5 into main Aug 30, 2026
24 checks passed
@rbuergi
rbuergi deleted the fix/2641-self-typed-user-row-unhealed branch August 30, 2026 07:28
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.

2 participants