Skip to content

fix(access): render denied subject thumbnails as benign fallback (#434) - #520

Merged
rbuergi merged 2 commits into
mainfrom
fix/434-access-thumbnail-denied-fallback
Jul 18, 2026
Merged

rbuergi merged 2 commits into
mainfrom
fix/434-access-thumbnail-denied-fallback

Conversation

@rbuergi

@rbuergi rbuergi commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

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 AccessAssignment row renders a MeshNodeThumbnailControl bound 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, throwing UnauthorizedAccessException.

Two defects amplified that expected denial into a storm:

  • MeshNodeThumbnailView.BindData's onError forwarded any stream error — including this denial — straight to SurfaceError → LogError + PortalErrorSink.Report (the toast) + StateHasChanged.
  • AccessControlLayoutArea.AccessControl recomposed the whole tab on every CombineLatest emission, 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 existing AreaErrorClassifier.IsExpectedUserActionFailure (already true for UnauthorizedAccessException). BindData's onError now: on a benign denial keeps the seeded Title/Description (the assignment row already supplies subject id + roles), renders the initials-avatar fallback, logs at Debug, and does not call SurfaceError. Only a genuine infra fault surfaces, once. (Precedent: ThreadMessageBubbleView, PortalErrorReporting already swallow benign access-denied / NotFound.)

2. Stabilize the tab composition. BuildPage depends only on the node's display name + the isAdmin bool, so each CombineLatest input is projected down to exactly that and DistinctUntilChanged'd. An unrelated node version bump or a repeated identical isAdmin emission 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):

  • Integration: as a non-member viewer, Mesh.GetMeshNodeStream(subjectInPeerPartition) (exactly what BindData subscribes) is denied; asserts the real UnauthorizedAccessException surfaces (not a hang) and ShouldSurfaceStreamError classifies it false (no toast).
  • Boundary: access-denied → swallow (fallback); genuine infra fault → surface once.

Revert-proven: the ShouldSurfaceStreamError seam doesn't exist on main. Local build green with CI flags (-c Release -warnaserror -p:CIRun=true, 0 warnings) for MeshWeaver.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 — ShouldSurfaceStreamError predicate
  • src/MeshWeaver.Blazor/Components/MeshNodeThumbnailView.razor.cs — onError classification
  • src/MeshWeaver.Graph/AccessControlLayoutArea.cs — composition stabilization (DistinctUntilChanged)
  • test/MeshWeaver.AccessControl.Test/AccessThumbnailDeniedFallbackTest.cs — new tests

🤖 Generated with Claude Code

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>
@github-actions

github-actions Bot commented Jul 18, 2026 •

Copy link
Copy Markdown
Contributor

Test Results

   60 files  + 1     60 suites  +1   28m 38s ⏱️ -7s
6 857 tests +61  6 554 ✅ +61  303 💤 ±0  0 ❌ ±0 
6 912 runs  +61  6 609 ✅ +61  303 💤 ±0  0 ❌ ±0 

Results for commit 7c464d1. ± Comparison against base commit 87a264b.

♻️ This comment has been updated with latest results.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

   11 files  + 1     11 suites  +1   4m 10s ⏱️ + 1m 11s
1 065 tests +59  1 065 ✅ +59  0 💤 ±0  0 ❌ ±0 
1 120 runs  +59  1 120 ✅ +59  0 💤 ±0  0 ❌ ±0 

Results for commit 7c464d1. ± Comparison against base commit 87a264b.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 4)

2 182 tests  ±0   1 994 ✅ ±0   4m 32s ⏱️ +4s
   12 suites ±0     188 💤 ±0 
   12 files   ±0       0 ❌ ±0 

Results for commit 7c464d1. ± Comparison against base commit 87a264b.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

1 259 tests  ±0   1 255 ✅ ±0   5m 18s ⏱️ +16s
   11 suites ±0       4 💤 ±0 
   11 files   ±0       0 ❌ ±0 

Results for commit 7c464d1. ± Comparison against base commit 87a264b.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

698 tests  +2   594 ✅ +2   5m 20s ⏱️ -37s
 10 suites ±0   104 💤 ±0 
 10 files   ±0     0 ❌ ±0 

Results for commit 7c464d1. ± Comparison against base commit 87a264b.

@github-actions

Copy link
Copy Markdown
Contributor

Test Results (shard 5)

766 tests  ±0   765 ✅ ±0   4m 30s ⏱️ - 1m 2s
 12 suites ±0     1 💤 ±0 
 12 files   ±0     0 ❌ ±0 

Results for commit 7c464d1. ± Comparison against base commit 87a264b.

@github-actions

github-actions Bot commented Jul 18, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

887 tests  ±0   881 ✅ ±0   4m 46s ⏱️ +2s
  4 suites ±0     6 💤 ±0 
  4 files   ±0     0 ❌ ±0 

Results for commit 7c464d1. ± Comparison against base commit 87a264b.

♻️ This comment has been updated with latest results.

@rbuergi
rbuergi merged commit 0be715a into main Jul 18, 2026
21 of 23 checks passed
@rbuergi
rbuergi deleted the fix/434-access-thumbnail-denied-fallback branch August 5, 2026 10:45
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.

Access & Control tab: permission-denied subject thumbnails cause repeated error toasts, flicker, and overlapping layout

1 participant