Skip to content

fix(positions): SignalR hub, delete cascade for terminal slots, common.remove - #200

Merged
windischb merged 3 commits into
developfrom
fix/position-signalr-hub
Aug 15, 2026
Merged

fix(positions): SignalR hub, delete cascade for terminal slots, common.remove#200
windischb merged 3 commits into
developfrom
fix/position-signalr-hub

Conversation

@windischb

@windischb windischb commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

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 PositionActions SignalR hub

position.store.ts has used enableSignalR: true since MG-FT-01, but no hub with that message name ever existed. Every admin connect logged Method 'PositionActions.Subscribe' not found!, and the Positions grid never updated live. PositionHub mirrors ServiceAccountHub: realm-scoped stream over subject "Position", gated by position: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/Active and 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 TerminalEnrollmentRevoked per slot and stages the client deletion via StageDeleteTerminalClientAsync into 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 receive PositionTerminalStatusChanged with Revoked.

New test Deleting_a_position_revokes_its_slots_and_deletes_their_clients pins slot status Revoked and the soft-deleted client.

3. common.remove did not exist

The 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 build clean.
  • UI checked in the browser on 0.10.0-beta.22, logged in via recover 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

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
windischb enabled auto-merge (squash) August 15, 2026 05:35
@windischb
windischb disabled auto-merge August 15, 2026 05:39
windischb and others added 2 commits August 15, 2026 07:46
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>
@windischb windischb changed the title fix(positions): add the missing PositionActions SignalR hub fix(positions): SignalR hub, delete cascade for terminal slots, common.remove Aug 15, 2026
@windischb
windischb merged commit 51b7967 into develop Aug 15, 2026
8 checks passed
@windischb
windischb deleted the fix/position-signalr-hub branch August 15, 2026 09:09
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.

1 participant