feat(positions): create terminal clients from the client side - #201
Merged
Conversation
The staffing counterpart of the client_credentials <-> ServiceAccount rule:
a client carrying the staffing grant must reference an existing Position or
inline-create one (never both). CreateClientAsync diverts terminal-intent
requests to a dedicated branch that delegates the client build to
StageCreateTerminalClient (the single producer of the fixed terminal
profile), stages position / grants / enrollment stream, and commits
everything in one SaveChanges. client_id follows the
{position}.terminal.{suffix} convention; DCR and the generic PUT path are
guarded against staffing clients.
Frontend mirrors the ServiceAccount pattern in ClientDetails: staffing
grant option (feature-gated), position picker or PositionDetails draft
modal (new draftOnly mode), terminal slot fields, fixed-profile hints.
RP-IDs are now inherited and locked once a position has slots - passkeys
hang off the RP-ID, so every slot of a position shares it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d, born only in the position modal Design decision (Bernhard): a terminal client is the technical footprint of a slot, not a configurable OAuth client - so the client grid keeps it visible as fleet inventory (new Terminal column naming the owning position) but read-only: double-click deep-links into the position modal, mirroring the SA-managed-client rule, and a direct fragment open degrades to a viewer (close-only footer + "Zur Position"). The staffing-grant create path disappears from the client modal (grant no longer offered; terminal block, position draft and draftOnly mode removed). The API path with all invariants and its 13 integration tests stays - automation can still create position + slot + client in one save. OAuthClientDto now carries LinkedPositionPrincipalId to drive the column, the redirect and the viewer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… switch The position modal is tabbed but ran on the cap-to-content MODAL_MD size, so switching between the short General tab and the list tabs resized the panel - against the modal size contract. Same fix as .user-edit-frame in UserDetails: the route stays cap-to-content, the component pins a 60vh body (create included, it is tabbed too); long grant/slot/session lists scroll inside the frame. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The model behind positions/terminals explained plainly: the four building blocks, the three links (grant / slot assignment / enrollment), why a position is not a group (distributor vs actor, same person = two actors on two devices), what the audit attests (unlock, not actions), which principal for which job - with inline SVG diagrams. Ends with the accepted design direction (ADR 0003: proof/device-binding menus, position-owned tokens, realm guard rails, multi-position terminals) clearly marked as roadmap. Linked from the admin workflow page and the sidebar. 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.
Was
„Ich will beides — wie in Service Accounts", präzisiert nach Design-Diskussion: Terminal-Clients entstehen in der UI ausschließlich im Positions-Modal; der Client-Seite-Anlegeweg existiert als API-Pfad (Automation), und im OAuth-Client-Grid bleiben Terminal-Clients als read-only Geräte-Inventar sichtbar.
Die tragende Regel (Terminal-Gegenstück zur
client_credentials⇔ ServiceAccount-Kopplung): ein Client mit dem Staffing-Grant braucht zwingend eine Position — referenziert (LinkedPositionPrincipalId) oder inline neu angelegt (NewPosition), nie beides.Backend
CreateClientAsyncerkennt Terminal-Absicht (Staffing-Grant oder Positions-/Terminal-Felder) und biegt in einen eigenen Zweig ab, der anStageCreateTerminalClientdelegiert — den einzigen Produzenten des Fixprofils (public, secretless, DPoP-pflichtig, Reference-Tokens, exakt device_code + refresh_token + staffing). Das Profil kann nicht abdriften.PositionPrincipalCreatedEvent), inkl. gestagter Grant-User;TerminalEnrollment-Stream startet im selben Save. EinSaveChangesAsync— Position, Grants, Slot und Client existieren zusammen oder gar nicht.{position}.terminal.{8-Zeichen-Suffix}(auto-generiert, Audit liest die Position aus der Kennung).position:writezusätzlich zuoauth-client:writeund 404t bei ausgeschaltetem Feature-Flag.OAuthClientDtoträgt jetztLinkedPositionPrincipalId;OAuthClientCreatedDtoträgtCreatedPosition+CreatedTerminalId; der Endpoint dispatcht Position-/Terminal-SignalR-Events.Frontend
Verifiziert
TerminalClientFromClientSideTests) für den API-Anlegeweg: beide Link-Modi, Ausschließlichkeit, alle Guards, Atomarität. Positions+OAuth-Suite 79/79, Unit 1519/1519 grün.201mit Fixprofil + Konvention-ClientId; Terminal-Spalte zeigt die Position; Viewer + „Zur Position"-Sprung landet im Positions-Modal; Staffing-Grant im Create-Picker abwesend; RP-ID-Sperre beim Folge-Slot; Delete-Kaskade (fix(positions): SignalR hub, delete cascade for terminal slots, common.remove #200) live mitverifiziert.docs/admin/positions.md(Terminal-Clients in Grid/Modal + API-Pfad),docs/integrate/position-terminals.md.🤖 Generated with Claude Code