fix(positions): SignalR hub, delete cascade for terminal slots, common.remove - #200
Merged
Conversation
The Pinia store has subscribed with enableSignalR: true since MG-FT-01, but no matching hub ever existed. Every admin session therefore logged "Method 'PositionActions.Subscribe' not found!" on connect (and again on every reconnect), and the Positions grid never updated live — a create in one tab stayed invisible in another until a manual reload. The hub mirrors ServiceAccountHub: realm-scoped stream of the DataEventDispatcher notifications with subject "Position", gated by position:read. It additionally returns an empty stream while the PositionTerminals flag is off, matching the defense-in-depth the REST surface already applies. Pre-existing since MG-FT-01; the rename in #198 only changed the name in the error message from FunctionActions to PositionActions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
windischb
enabled auto-merge (squash)
August 15, 2026 05:35
windischb
disabled auto-merge
August 15, 2026 05:39
The staged terminal-slot row in the position modal falls back to the English "Remove" because common.remove never existed — the German "Entfernen" at line 322 sits in the passkey block. Verified in the running container before and after. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A soft-deleted position left its slots Pending/Active and their managed OAuth clients fully live (IsDeleted=false) — orphans whose LinkedPositionPrincipalId pointed at a principal that no longer existed. The per-slot revoke path always cleaned up correctly; only the position-level delete never cascaded. The delete now appends TerminalEnrollmentRevoked per slot and stages the client deletion through StageDeleteTerminalClientAsync into the same unit of work as the delete event. After the commit each slot's device is cut off immediately (RevokeTokensByApplicationIdAsync — the clients hold reference tokens for exactly this) and consumers get a PositionTerminalStatusChanged with status Revoked. Found by clicking through the running container, not by a test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Three small defects found by actually running the local container and clicking through the admin UI — batched into one PR rather than one pipeline run each.
1. Missing
PositionActionsSignalR hubposition.store.tshas usedenableSignalR: truesince MG-FT-01, but no hub with that message name ever existed. Every admin connect loggedMethod 'PositionActions.Subscribe' not found!, and the Positions grid never updated live.PositionHubmirrorsServiceAccountHub: realm-scoped stream over subject"Position", gated byposition:read, empty while the feature flag is off.Pre-existing since MG-FT-01 — the rename in #198 only changed the name in the error message.
2. Deleting a position orphaned its terminal slots
A soft-deleted position left its slots
Pending/Activeand their managed OAuth clients fully live (IsDeleted=false), pointing at a principal that no longer existed. Reproduced in the running container before fixing. The per-slot revoke path always cleaned up; only the position-level delete never cascaded.The delete now appends
TerminalEnrollmentRevokedper slot and stages the client deletion viaStageDeleteTerminalClientAsyncinto the same unit of work as the delete event. After the commit each device is cut off immediately (RevokeTokensByApplicationIdAsync— the clients carry reference tokens for exactly this) and consumers receivePositionTerminalStatusChangedwithRevoked.New test
Deleting_a_position_revokes_its_slots_and_deletes_their_clientspins slot statusRevokedand the soft-deleted client.3.
common.removedid not existThe staged terminal-slot row fell back to the English "Remove"; the German "Entfernen" sits in the passkey block, not under
common.Verification
Local, against the running container and the test suite:
dotnet test --filter ~Positions: 57/57 green (56 before, +1 new cascade test).Modgud.Tests.Unit: 1519/1519 green.dotnet buildclean.0.10.0-beta.22, logged in viarecover magic-link: create dialog shows 3 tabs (no Staffing sessions), edit shows 4; staging a slot works and the row shows "BEIM SPEICHERN"; turning terminal use off with a staged slot raises the tab warning and disables Save; one save created position + slot + OAuth client.🤖 Generated with Claude Code