Repository navigation
Pin #1127: the User NodeType declaration is never treated as an account - #1157
Conversation
memex-cloud logged 27x `42P01: relation "user.mesh_nodes" does not exist`, every one a CreateNode at the literal path User/_Memex/AiSettings. The path was wrong, not the schema: `User` is the SELF-TYPED NodeType declaration node (UserNodeType.CreateMeshNode sets NodeType = "User" on the node at path "User"), so AiSourcesInstallHook's pathless `nodeType:User` user enumeration returned it alongside the real accounts and reconciled it as a user - once per installed package, on every pod boot's repair pass (27 = 3 pod boots x ~9 packages). The per-user Catch reduced each failure to log noise; on PG nothing ever persisted (path:User/_Memex/AiSettings is empty on the live portal), and no real user's settings were touched - the hook reconciled every real account correctly the whole time. The enumeration fix (drop the declaration id) already landed on main via PR #1074 (5408527) but shipped with NO test - the regression is silent by construction, since the Catch swallows the failing write. This adds the end-to-end pin on the monolith mesh, where the phantom write SUCCEEDS and leaves queryable evidence: with the filter removed the test goes red (User/_Memex/AiSettings exists), with it in place real users receive the package's agent + skill sources and the declaration path stays absent. Sweep result: no other writer constructs a literal User/... path. All remaining `nodeType:User` consumers are content-filtered lookups (the declaration carries a NodeTypeDefinition, so content.email/objectId never match) or read-only; declaration nodes are served by the static query provider and never pass through CreateNode, so post-creation seed handlers cannot fire for them. Fixes #1127 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UYLgoPK1qLtyxZ2ixdFtj5
There was a problem hiding this comment.
Pull request overview
Adds an end-to-end regression pin for #1127 ensuring AiSourcesInstallHook never treats the self-typed User NodeType declaration as a real account, plus a user-visible “What’s New” entry documenting the fix.
Changes:
- Adds a monolith integration test that runs the install hook and verifies it only writes AI source rows for real users (and never creates
User/_Memex/AiSettings). - Adds a
Category: FixWhat’s New entry describing the eliminated recurring startup/install errors.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| test/MeshWeaver.Hosting.Monolith.Test/AiSourcesInstallHookTest.cs | New regression test covering the “User declaration treated as account” failure mode end-to-end. |
| src/MeshWeaver.Documentation/Data/WhatsNew/2026-08-10-ai-settings-no-longer-written-for-a-phantom-account.md | New release note entry documenting the fix and its impact. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Test Results (shard 2)1 923 tests - 95 1 922 ✅ - 94 6m 15s ⏱️ -30s Results for commit cc2d39e. ± Comparison against base commit e6c09b1. This pull request removes 305 and adds 210 tests. Note that renamed tests count towards both.♻️ This comment has been updated with latest results. |
Test Results (shard 1)1 289 tests +96 1 288 ✅ +95 6m 29s ⏱️ +30s Results for commit cc2d39e. ± Comparison against base commit e6c09b1. This pull request removes 210 and adds 306 tests. Note that renamed tests count towards both.♻️ This comment has been updated with latest results. |
…tBindingTest, fixed by #1153
…ndex The assertion read GetQuery(...).FirstAsync() and expected an empty result. That is the one case a query cannot settle: it is eventually consistent, so an empty first emission is equally consistent with 'the phantom was never written' and 'the phantom was written and the index has not caught up' — and the second is precisely the regression this test exists to catch. As written it could only ever pass, including against the pre-fix code, which makes it a false negative rather than a pin. Now reads GetMeshNodeStream(phantomPath), which is authoritative: an absent node surfaces as a routing NotFound (OnError/DeliveryFailureException) while a present one arrives as OnNext, so the two outcomes are different signals rather than the same empty result. Same shape as CompileActivityNoPhantomPathTest, which pins the identical 'leaf never created under a real ancestor' phantom. The reviewer's other point — unused usings failing under -warnaserror via CS8019 — does not apply: CS8019 is not enabled here and the project builds clean with -c Release -warnaserror, verified before and after this change. Test passes (1/1); MeshWeaver.Hosting.Monolith.Test builds clean under CI flags.
|
Addressed both review points in The phantom assertion is the substantive one, and the reviewer is right — as written the test could not fail. It read It now reads On the unused usings: that one does not apply. CS8019 is not enabled in this project, and Test passes 1/1. |
Fixes #1127
Verdict on the path question
The path
User/_Memex/AiSettingswas wrong — a path-construction bug, not a missing schema.Useris the self-typed NodeType declaration node:UserNodeType.CreateMeshNode()deliberately setsNodeType = "User"on the declaration at pathUser, so any pathlessnodeType:Userquery returns the declaration alongside real accounts.AiSourcesInstallHook.Users()enumerated users exactly that way and reconciled the declaration's Id ("User") as an account — once per installed package, on every pod boot's repair pass. 27 occurrences = 3 pod boots × ~9 installed packages (Store, Edu, Essentials, Skill, Agent, Publish, DataModelling, Collaboration, AgenticOffice, Feedback), each burst landing within the same second per pod.What happened to the failed writes
Nothing was lost. Live-portal evidence (read-only):
path:User/_Memex/AiSettings→ 0 results — every phantom write was aborted by the 42P01, none persisted.rbuergi/_Memex/AiSettingsis healthy,lastModifiedBy: system-securitytoday 12:55Z, carrying exactly the per-package agent/skill source rows — the same repair pass reconciled every real account correctly the whole time. The failing writes carried only machine-generated source rows for a nonexistent account; no user-entered settings were involved.The fix, and what this PR adds
The enumeration fix (drop the declaration id) already landed on main in PR #1074 (
540852750) — but with no test, and the regression is silent by construction (the per-userCatchreduces the failing write to a log line). This PR adds the end-to-end regression pin on the monolith mesh, where the phantom write succeeds and leaves queryable evidence:User/_Memex/AiSettingsexists afterOnPartitionInstalled("found 1 item(s)").Also a
Category: FixWhat's New entry for the user-visible half of #1127 (recurring 42P01 log errors + the auto-filed incident), which PR #1074's entry did not cover.Same-bug sweep (negative results)
AiSourcesInstallHookis the only every-user fan-out writer. All othernodeType:Userconsumers are content-filtered lookups (content.email:/content.objectId:— the declaration's content is aNodeTypeDefinition, so they never match) or read-only (DevLogin list,UserIdentityCache, pickers).AiSettingsSeedHandleretc., keyed on NodeTypeUser) cannot fire for the declaration: static/declaration nodes are served byStaticNodeQueryProviderat query time and never pass through theCreateNodepipeline.User/...write path; remaining"User/"literals are reads/strips of the legacyUser/{id}catalog-mirror shape.Prod follow-up
None needed in the database (nothing persisted, no real data affected). The 42P01 noise on memex-cloud stops when the pods roll to an image containing PR #1074 (merged this morning); occurrences after the merge are the pre-fix image still running.
Verification
dotnet build test/MeshWeaver.Hosting.Monolith.Test -c Release -warnaserror— clean (0 warnings, 0 errors), test file included.FullyQualifiedName~AiSourcesInstallHookTest).🤖 Generated with Claude Code
https://claude.ai/code/session_01UYLgoPK1qLtyxZ2ixdFtj5