Repository navigation
fix(access): render denied subject thumbnails as benign fallback (#434) - #520
Merged
Merged
Conversation
The Access & Control tab renders each AccessAssignment row via a MeshNodeThumbnailControl bound to the SUBJECT's node path. A subject often lives in a partition the viewer is not a member of (two users can share access to a node without reading each other's user partitions), so the RLS gate denies the subject read BY DESIGN — MeshNodeStreamCache .GateOnRead throws UnauthorizedAccessException. MeshNodeThumbnailView.BindData forwarded that denial straight to SurfaceError → LogError + PortalErrorSink.Report (the toast) + StateHasChanged, and AccessControlLayoutArea recomposed the whole tab on every upstream emission — re-instantiating each thumbnail, re-subscribing each denied subject stream, re-toasting. Result: repeated permission-denied toasts, flicker, and overlapping cards. Root-cause fix: 1. Classify the denial as benign. New tested predicate MeshNodeThumbnailControl.ShouldSurfaceStreamError delegates to AreaErrorClassifier.IsExpectedUserActionFailure; BindData's onError keeps the seeded title/description + initials-avatar fallback and logs at Debug for an access-denied read, and only surfaces a genuine infra fault (once). 2. Stabilize the tab composition. BuildPage depends only on the node's display name + the isAdmin bool, so project each CombineLatest input down to exactly that and DistinctUntilChanged — an unrelated node version bump or a repeated isAdmin emission no longer recomposes the tab (which is what tore down + re-subscribed the rows). Binding stays live; no .Take(1). Test: AccessThumbnailDeniedFallbackTest (MonolithMeshTestBase + RLS) — a non-member viewer's real subject-stream read is denied, and the fix classifies it as no-surface (no toast). Revert-proven: the ShouldSurfaceStreamError seam does not exist on main. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Contributor
Contributor
Contributor
Contributor
Contributor
Contributor
Contributor
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.
Fixes #434.
Problem
Opening a node's Access & Control tab produced repeated permission-denied error toasts (one per assignment row), wild flickering, and overlapping cards.
Each
AccessAssignmentrow renders aMeshNodeThumbnailControlbound to the subject's node path. A subject usually lives in a partition the viewer isn't a member of (two users can share access to a node without being able to read each other's user partitions), so the RLS gate (MeshNodeStreamCache.GateOnRead) denies the subject read by design, throwingUnauthorizedAccessException.Two defects amplified that expected denial into a storm:
MeshNodeThumbnailView.BindData's onError forwarded any stream error — including this denial — straight toSurfaceError→LogError+PortalErrorSink.Report(the toast) +StateHasChanged.AccessControlLayoutArea.AccessControlrecomposed the whole tab on everyCombineLatestemission, re-instantiating each thumbnail → new denied subscription → new toast + log +StateHasChanged.Fix (root cause)
1. Classify permission-denied as a benign fallback (the primary fix). New tested predicate
MeshNodeThumbnailControl.ShouldSurfaceStreamError(ex)delegates to the existingAreaErrorClassifier.IsExpectedUserActionFailure(already true forUnauthorizedAccessException).BindData's onError now: on a benign denial keeps the seededTitle/Description(the assignment row already supplies subject id + roles), renders the initials-avatar fallback, logs at Debug, and does not callSurfaceError. Only a genuine infra fault surfaces, once. (Precedent:ThreadMessageBubbleView,PortalErrorReportingalready swallow benign access-denied / NotFound.)2. Stabilize the tab composition.
BuildPagedepends only on the node's display name + theisAdminbool, so eachCombineLatestinput is projected down to exactly that andDistinctUntilChanged'd. An unrelated node version bump or a repeated identicalisAdminemission no longer recomposes the tab — which is what tore down + re-subscribed the unchanged rows. The binding stays live (no.Take(1)); only no-op re-renders are suppressed.Fixing #1 removes the toasts + log storm + per-error
StateHasChanged; #2 removes the residual flicker/overlap.Test
AccessThumbnailDeniedFallbackTest(MonolithMeshTestBase+AddRowLevelSecurity, no mocking):Mesh.GetMeshNodeStream(subjectInPeerPartition)(exactly whatBindDatasubscribes) is denied; asserts the realUnauthorizedAccessExceptionsurfaces (not a hang) andShouldSurfaceStreamErrorclassifies it false (no toast).Revert-proven: the
ShouldSurfaceStreamErrorseam doesn't exist on main. Local build green with CI flags (-c Release -warnaserror -p:CIRun=true, 0 warnings) forMeshWeaver.Graph,MeshWeaver.Blazor, and the test project; both tests pass.The flicker/overlap final confirmation is best verified in a live portal, but the composition change is in-repo and covered by the reasoning above.
Files changed
src/MeshWeaver.Graph/MeshNodeThumbnailControl.cs—ShouldSurfaceStreamErrorpredicatesrc/MeshWeaver.Blazor/Components/MeshNodeThumbnailView.razor.cs— onError classificationsrc/MeshWeaver.Graph/AccessControlLayoutArea.cs— composition stabilization (DistinctUntilChanged)test/MeshWeaver.AccessControl.Test/AccessThumbnailDeniedFallbackTest.cs— new tests🤖 Generated with Claude Code