Repository navigation
fix(registry): an instance's FIRST plugin grant always failed - #854
Conversation
…node that does not exist
Found by rolling this out: granting any instance its first plugin failed with
Update aborted: no initial state arrived for 'Admin/_PluginGrant/memex-cloud'
within 30s
The admin grant tab read-modify-wrote through GetMeshNodeStream(path).Update, but
a grant node does not exist until the first grant is made, and Update waits for an
initial state that never arrives. So the tab could edit an existing grant and never
create one — which is every instance, once.
Now reads the node one-shot by exact path (never a query — eventually consistent,
and it would happily drop an entry another admin just added), folds the entry in or
out, and writes with CreateOrUpdateNode, which covers create and update alike. Grant
stays idempotent; Revoke stays a removal.
The three live grants (memex-cloud, memex, atioz) were written by script during the
rollout because of this; with the fix the tab can create them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01398UXRgmg2E3xS7xkTHdAo
There was a problem hiding this comment.
Pull request overview
Fixes the “first plugin grant always fails” bug in the plugin registry admin UI by avoiding GetMeshNodeStream(path).Update(...) on a grant path that may not exist yet, and switching to a read + upsert flow so the first grant can create the missing Admin/_PluginGrant/{instanceId} node.
Changes:
- Replace stream
Update(...)(which hangs when the node doesn’t exist) withHub.GetMeshNode(path)+IMeshService.CreateOrUpdateNode(...). - Ensure grant writes remain idempotent by de-duping matching
(source, package)entries before re-adding on grant.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| var grant = existing?.ContentAs<PluginGrant>(host.Hub.JsonSerializerOptions) | ||
| ?? new PluginGrant { InstanceId = instanceId }; | ||
| // Drop any identical entry first, so Grant is idempotent and Revoke is a removal. | ||
| var entries = grant.Entries | ||
| .Where(e => !(string.Equals(e.Source, entry.Source, StringComparison.OrdinalIgnoreCase) |
Test Results (shard 2)2 021 tests 2 020 ✅ 4m 47s ⏱️ Results for commit cb37339. ♻️ This comment has been updated with latest results. |
Test Results (shard 0)367 tests 363 ✅ 5m 7s ⏱️ Results for commit cb37339. ♻️ This comment has been updated with latest results. |
Test Results (shard 5)1 530 tests 1 426 ✅ 5m 26s ⏱️ Results for commit cb37339. ♻️ This comment has been updated with latest results. |
Test Results (shard 3) 10 files 10 suites 4m 26s ⏱️ Results for commit cb37339. ♻️ This comment has been updated with latest results. |
Test Results (shard 4)1 064 tests 1 057 ✅ 5m 12s ⏱️ Results for commit cb37339. ♻️ This comment has been updated with latest results. |
Test Results (shard 1)1 526 tests 1 339 ✅ 7m 8s ⏱️ Results for commit cb37339. ♻️ This comment has been updated with latest results. |
Test Results 64 files 64 suites 32m 8s ⏱️ Results for commit cb37339. |
Found by rolling out the instance-key registry (#842) against memex-cloud.
The failure
Granting any instance its first plugin failed with:
The admin grant tab read-modify-wrote through
GetMeshNodeStream(path).Update(...). But a grant node does not exist until the first grant is made, andUpdatewaits for an initial state that never arrives. So the tab could edit an existing grant and never create one — which is every instance, exactly once, i.e. always.The fix
Read the node one-shot by exact path, fold the entry in or out, then write with
CreateOrUpdateNode, which covers create and update alike.Grantstays idempotent;Revokestays a removal.Deliberately a one-shot exact-path read, not a query:
QueryAsync/ObserveQueryare eventually consistent, so a lagged read could silently drop an entry another admin had just added.Context
The three live grants (
memex-cloud,memex,client-i) were written by script during the rollout because of this defect. With the fix, the tab can create them — which matters for the next instance anyone registers.Registry enforcement is now live:
https://memex.meshweaver.cloud/api/plugins401s anonymous callers, and each instance sees exactly its grant (memex=Plugins/*+Reinsurance/UWDeepfield, and notably notClaimsDeepfield).Verification
MeshWeaver.PluginCatalogbuilds Release with-warnaserror;MeshWeaver.PluginCatalog.Testrunning. The grant-matching logic itself is already pinned byPluginGrantTest; this change is the write path around it.🤖 Generated with Claude Code
https://claude.ai/code/session_01398UXRgmg2E3xS7xkTHdAo