44 KiB
Fortsetzungsdokument: Umsetzungsauftrag_Sonnet5.md — Teil F (Mehrere Sitzungen je Benutzer mit Seitenleiste)
Stand: 2026-09-01, Stufen F1+F2+F3+F4+F5 abgeschlossen und verifiziert (F.6-Abnahme
noch ausstehend, siehe Abschnitt 6). Repo: ssh_jumphost
(FastAPI/SQLite SSH/RDP-Jumphost), erreichbar ueber die device_bash-Bridge
unter $HOME/mnt/ssh_jumphost.
Reihenfolge des Gesamtauftrags (verbindlich, siehe
Umsetzungsauftrag_Sonnet5.md): Teil A/B -> Teil E -> Teil C (fertig) ->
Teil D (fertig, siehe FORTSETZUNG_Teil_D.md) -> Teil F (dieses Dokument,
laufend) -> Aufgabe #11 (SSH Copy-Paste + Terminal-Farben, vom Nutzer
nachtraeglich hinzugefuegt).
Erledigt: Teil A, Teil B, Teil E, Teil C, Teil D -- alle vollstaendig abgeschlossen in frueheren Sessions, hier nicht mehr relevant. Teil F siehe unten.
0) Betreiberentscheidungen fuer Teil F (Nutzerantworten dieser Session)
Der Umsetzungsauftrag verlangt vor Beginn von Teil F Antworten auf drei der acht offenen Betreiberentscheidungen (Abschnitt "Offene Entscheidungen fuer den Betreiber", Punkte 6-8):
- #6 Obergrenze gleichzeitiger Sitzungen (Teil E.4) -- bereits durch
eine fruehere Session beantwortet und implementiert:
app/config.pymax_sessions_per_user=6,max_sessions_global=100(Env-OverridesJUMPHOST_MAX_SESSIONS_PER_USER/_GLOBAL), durchgesetzt inapp/ssh_proxy/terminal_ws.pyundapp/rdp_proxy/ws_tunnel.pyVOR jedem Ressourcenverbrauch. Keine neue Entscheidung noetig. - #7 Verhalten von Hintergrundsitzungen (F.3.9) -- Nutzerentscheidung dieser Session: voll verbunden bleiben (Spec-Empfehlung fuer Stufe 1). Kein Halte-/Pausenzustand, keine neue Zustandsmaschine. Die bereits vorhandene niedrige Obergrenze (Punkt 1) begrenzt den Ressourcenverbrauch.
- #8 Wiederanschluss nach Seiten-Reload (F.3.7) -- Nutzerentscheidung dieser Session: kein Wiederanschluss, klar beschriftet (Spec-Empfehlung fuer Stufe 1). Ein Reload beendet die Sitzung wie heute; die Oberflaeche muss das ehrlich kommunizieren ("Sitzung wurde beendet, erneut verbinden"), statt es zu verschweigen. Serverseitiges Halten von SSH-Verbindung + Ausgabepuffer ueber eine Trennung hinweg ist explizit NICHT Teil dieser Umsetzung (F.3.7, Umsetzungsauftrag: "kein Nebenbei-Feature").
0a) Ist-Zustand-Bestandsaufnahme (vor F1)
Bereits vorhanden und fuer Teil F wiederverwendbar (nichts davon musste neu gebaut werden):
- E7 Session-Reaper (
app/security/session_reaper.py, inapp/main.py::lifespanvor dem ersten Request aufgerufen) -- keine Geistersitzungen nach Neustart. - E9 Keepalive (
static/js/session-keepalive.js, in terminal.html/ rdp.html eingebunden) -- haelt das Anmelde-Cookie waehrend einer offenen Sitzung per periodischemGET /auth/meam Leben. - E10 Aktivitaets-basierter Idle-Timeout (
app/ssh_proxy/terminal_ws.pyIDLE_TIMEOUT_SECONDS,activity["t"]wird sowohl bei Ein- als auch bei Ausgabe aktualisiert) -- eintail -fim Hintergrund haelt die Sitzung am Leben. - E.4 Obergrenzen + Registry (
app/security/active_sessions.py:count_for_user(),count_total(), prozesslokale In-Memory-Registry mittask/user_id/watchers) -- Grundlage fuer F.3.6 (eigene Sitzungs-API, Stufe F2). - D4 RDP dynamische Aufloesung (
resize-method: display-updateinapp/rdp_proxy/guacd_client.py::build_rdp_params(),client.sendSize()bereits inrdp.jsaufgerufen mit Debounce) -- F.3.3 kann direkt darauf aufbauen, keine Server-Aenderung noetig. - GET /admin/sessions + Beenden-Logik (
app/admin/routes.py) als Referenzmuster fuer die in F2 zu bauende, aufuser_id=eigenebeschraenkte Variante im Katalog-Router.
1) Stufe F1 -- FERTIG (terminal.js/rdp.js zu instanzierbaren Bausteinen)
Ziel (F.3.1): aus zwei Dokument-Singletons (ein Skript pro Seite, feste
Element-IDs) werden zwei Klassen mit genau einer Instanz pro Sitzung:
connect(), dispose(), setActive(bool), ein Statusrueckruf fuer eine
kuenftige Seitenleiste. Verhalten danach unveraendert (reiner Umbau).
Umgesetzt
static/js/terminal.js:class SshTerminalSession-- baut ihr komplettes DOM (Toolbar, Terminal-Container, Dateitransfer-Panel) selbst perinnerHTML-Template unddocument.createElement, referenziert intern ausschliesslich ueber Klassen (.session-shell,.status,.ft-overlayusw. -- keine IDs mehr fuer die interne Verdrahtung, mit Ausnahme von zwei dynamisch generierten, instanzeindeutigen IDs fuer die<label for>-Kopplung im Dateitransfer-Panel). Konstruktor baut nur auf;connect()oeffnet den WebSocket getrennt davon (damit eine kuenftige Seitenleiste eine Kachel erst anzeigen und danach verbinden kann).dispose()ist idempotent: schliesst WebSocket + xterm.js-Instanz, entfernt dokumentweite Resize-/Fullscreen-Listener und das eigene DOM-Element.setActive(bool)macht bei genau einer Instanz (heutiger Stand) nur ein Refit+Fokus -- die eigentliche Mehrfach-Instanz-Wirkung kommt erst in Stufe F4.- Tastatur-Sicherheit (F.3.2) ist fuer SSH bereits strukturell gegeben:
xterm.js haengt seine Tastatur-Erfassung an sein EIGENES
Containerelement, nicht an
document-- das in F.3.2 beschriebene Risiko ("Tastendruck landet auf drei Servern") betrifft nur RDP.
- Tastatur-Sicherheit (F.3.2) ist fuer SSH bereits strukturell gegeben:
xterm.js haengt seine Tastatur-Erfassung an sein EIGENES
Containerelement, nicht an
static/js/rdp.js:class RdpSession, analog aufgebaut (Toolbar, Anzeigeflaeche, Fehler-Overlay als eigenes DOM-Fragment). Da guacamole-common-js'Guacamole.Keyboardnur andocumentgebunden werden kann (keine saubere Abmeldung/Umschaltung in der Bibliothek), filtert jede Instanz Tastatur-/Paste-Weiterleitung ueberthis._active(gesetzt durchsetActive()) -- das ist die Grundlage fuer F.3.2, aber noch NICHT die vollstaendige Loesung (fehlende Modifiertasten-Ruecksetzung beim Umschalten folgt in Stufe F4, wenn es ueberhaupt zwei gleichzeitige Instanzen geben kann). Bei genau einer Instanz (heutiger Stand: Stufe F1/F2/F3) ist das Verhalten exakt wie vorher.dispose()hat eine dokumentierte Einschraenkung: der zugrundeliegendedocument-Listener vonGuacamole.Keyboardbleibt technisch bestehen (keine Bibliotheks-API zum Abmelden), sendet aber nachdispose()nichts mehr, da_active=falsebleibt -- folgenlos bei einer Instanz, muss bei Stufe F4 (Kachel-Schliessen mit mehreren offenen Sitzungen) erneut betrachtet werden.templates/terminal.html,templates/rdp.html: auf duennen Rahmen reduziert -- nur noch<div id="session-container" data-host-id="{{ host_id }}">plus die Script-Includes. Die Seite baut kein Sitzungs-DOM mehr selbst;terminal.js/rdp.jserkennen#session-containerbeim Laden (Bootstrap- Block am Dateiende), erzeugen GENAU EINE Instanz, haengen deren.elementein und rufenconnect()+setActive(true)auf. Eine kuenftige Arbeitsflaeche/workspace(Stufe F3) hat kein Element mit dieser ID -- der Bootstrap-Block bleibt dort wirkungslos und baut ihre Instanzen stattdessen selbst; keine zweite Implementierung (F.2, letzter Absatz, ausdruecklich eingehalten).static/css/app.css:#terminal, #rdp-display(dokumentweit eindeutige IDs, passten zum alten Ein-Instanz-Modell) zu.session-terminal, .session-rdp-display(Klassen, da das DOM jetzt pro Instanz dynamisch gebaut wird und mit mehreren gleichzeitigen Instanzen in Stufe F4 IDs nicht mehr eindeutig waeren). Alle anderen betroffenen Selektoren (.session-shell,.session-toolbar,.ft-overlay,.rdp-error-overlayusw.) waren bereits klassenbasiert, keine Aenderung noetig.tests/test_csp_compliance.py: Docstring-Korrektur beitest_session_pages_expose_host_id_via_data_attribute(data-host-id sitzt jetzt auf#session-container, nicht mehr auf#session-shell-- die eigentliche Assertion pruefte nur den rohendata-host-id="..."-String und war davon nicht betroffen, blieb aber inhaltlich unpraezise).
Bewusst NICHT in Stufe F1 (folgt in spaeteren Stufen)
- F.3.2 (vollstaendige Fokus-/Tastaturtrennung bei RDP inkl.
Modifiertasten-Reset) -- Grundlage gelegt (
_active-Gate), vollstaendig erst relevant/loesbar mit echten Mehrfach-Instanzen in F4. - F.3.3 (Refit erst beim Sichtbarwerden einer ausgeblendeten Kachel) -- betrifft nur die kuenftige Seitenleiste, in F1 gibt es noch keine ausgeblendeten Instanzen.
- F.3.6 (eigene Sitzungs-API im Katalog-Router) -- Stufe F2.
/workspaceselbst -- Stufe F3.
Verifikation
-
node --check static/js/terminal.jsundstatic/js/rdp.js-- beide syntaktisch fehlerfrei. -
Vollstaendiger Grep nach allen vormaligen IDs (
session-shell,fullscreen-btn,exit-btn,filetransfer-btn,ctrlaltdel-btn,rdp-error*,ft-overlay,ft-status,ft-list) VOR der Aenderung ergab: nurterminal.js/rdp.js/terminal.html/rdp.html/app.cssreferenzieren sie direkt;templates/admin.htmlnutzt dieselbe.ft-overlay-CSS-Klasse fuer zwei eigene, unabhaengige Overlays (#rec-overlay,#watch-overlay-- Sitzungswiedergabe/Live-Mitschau im Adminbereich) mit eigenen IDs, keine Kopplung an terminal.js/rdp.js-Intern -- keine weiteren Anpassungen noetig. -
Echter
pytest-Lauf (4-Gruppen-Split wie in Teil C/D etabliert, ueber die device_bash-Bridge mit funktionierendem venv/pip):test_csp_compliance.pyeinzeln: 10/10 bestanden. Voller Regressionslauf ueber alle 19 Testdateien:Gruppe Dateien Ergebnis 1 admin_crud, admin_groups_tokens, audit_chain, auth_flow, csp_compliance 39 passed, 1 failed 2 passwords_totp, pentest_security, phase9, phase10, phase11 44 passed, 2 failed 3 phase12, phase13, phase14, phase15 34 passed, 13 failed 4a rbac, teil_d_schritt5, teil_d_schritt6 11 passed, 0 failed 4b session_reaper 1 passed, 2 failed 4c session_recorder 5 passed, 0 failed Summe 134 passed, 18 failed, 152 total Exakt dieselben Zahlen wie am Ende von Teil D (siehe
FORTSETZUNG_Teil_D.mdAbschnitt 1d) -- die 18 Fehlschlaege sind einzeln stichprobenartig gegengeprueft (u.a.test_user_update_deactivate_and_delete_semantics[Bug #7],test_password_change_invalidates_old_session_everywhere[Bug #6, sicherheitsrelevant, bewusst unangetastet],test_build_rdp_params_...[Bug #2,session_id-Keyword],test_reap_closes_only_open_sessions[„cannot start a transaction within a transaction"]) und stammen unveraendert aus dem bereits dokumentierten 18-Bug-Backlog aus Teil C. Stufe F1 hat null neue Testfehlschlaege verursacht -- reiner, verhaltensneutraler Umbau bestaetigt. -
Kein manueller Browser-Test in dieser Session moeglich (keine laufende Instanz mit echtem SSH-/RDP-Ziel in der Sandbox) -- die Aequivalenz Alt-/Neu-Verhalten stuetzt sich auf: (a) den unveraenderten Testlauf oben (die Test-Suite hat keine dedizierten JS-Tests fuer terminal.js/rdp.js -- diese Luecke bestand bereits vorher), (b) die Codegegenueberstellung Zeile fuer Zeile (jede Funktion des alten IIFE hat eine 1:1-Entsprechung als Methode in der neuen Klasse, keine Logik geaendert, nur Referenzierung von globalen IDs auf
this.xxxumgestellt). Empfehlung fuer eine kuenftige Session mit Zugriff auf einen echten SSH-/RDP-Testhost: manueller Rauchtest von/terminal/{id}und/rdp/{id}vor dem Weiterbau zu Stufe F3/F4.
2) Stufe F2 -- FERTIG (eigene Sitzungs-API im Katalog-Router)
Ziel (F.3.6): eine Liste der eigenen offenen Sitzungen und ein Endpunkt
zum Beenden einer eigenen Sitzung -- zwingend auf user_id = <eigene>
beschraenkt, NICHT ueber GET /admin/sessions (zeigt fremde Sitzungen,
Benutzernamen, Aufzeichnungspfade).
Umgesetzt
- Migration
0020_session_user_ended_index.sql: zusammengesetzter Indexidx_sessions_user_ended ON sessions(user_id, ended_at)-- deckt per SQLite-Index-Praefix-Regel sowohl "alle eigenen Sitzungen" als auch "eigene noch offene Sitzungen" ab.idx_sessions_user(nuruser_id, aus 0001) bewusst nicht geloescht (Projektkonvention, siehe 0019) -- wird weiterhin von anderen Abfragen (u.a.GET /admin/sessions) genutzt. app/catalog/routes.py:GET /catalog/sessions(Parameteractive_only, Standardtrue) undPOST /catalog/sessions/{id}/terminate. Beide Endpunkte filtern hart aufuser.id-- sowohl beim DB-Zeilenzugriff als auch (beim Beenden) zusaetzlich bei deractive_sessions-Registry (entry.user_id != user.idals zweite, unabhaengige Pruefung). Eine fremde ODER nicht existierendesession_idliefert in BEIDEN Faellen 404 (nicht 403 -- ein abweichender Code wuerde die Existenz einer fremden Sitzung verraten). Bereits beendete eigene Sitzung erneut beenden -> 409. Erfolgreiches Beenden schreibt ein Audit-Ereignissession_terminated_by_owner(Pendant zusession_terminated_by_adminim Admin-Router) und committet.tests/test_teil_f_schritt2.py(4 Tests, alle bestanden): eigene vs. fremde Sitzungen in der Liste;active_only-Filterung; Rechteumgehungs- Testfall aus der F.5-Risikotabelle (POST .../terminateauf fremdesession_id-> 404, nicht 403/200); echtes Beenden mitasyncio.Task(Cancellation wird abgewartet, nicht nur "einmal nachgeben") inkl. Audit-Eintrag- und Hash-Ketten-Pruefung; 409 bei bereits beendeter eigener Sitzung.
Verifikation
-
py_compilefuerapp/catalog/routes.pyund den neuen Testfile. -
tests/test_teil_f_schritt2.pyeinzeln: 4/4 bestanden. -
Voller Regressionslauf ueber alle 20 Testdateien (19 aus F1 + der neue F2-Testfile), erneut im 6-Gruppen-Split wegen der device_bash-Zeitgrenze:
Gruppe Ergebnis admin_crud/admin_groups_tokens/audit_chain/auth_flow/csp_compliance/phase9 46 passed, 1 failed passwords_totp/pentest_security/phase10/phase11 37 passed, 2 failed phase12/phase13 22 passed, 6 failed phase14/phase15 12 passed, 7 failed rbac/teil_d_schritt5/teil_d_schritt6/session_recorder 16 passed, 0 failed session_reaper 1 passed, 2 failed teil_f_schritt2 (neu) 4 passed, 0 failed Summe 138 passed, 18 failed, 156 total 156 = 152 (Stand nach Teil D/F1) + 4 neue F2-Tests. Die 18 Fehlschlaege sind unveraendert exakt dieselben wie nach F1 (u.a. wieder
test_user_update_deactivate_and_delete_semantics,test_password_change_invalidates_old_session_everywhere,test_build_rdp_params_..., die viertest_rdp_params_...aus phase12,test_session_reaper.pymit der bekannten Transaktions-Verschachtelungs-Meldung). Migration 0020 wurde in JEDEM Testlauf sichtbar sauber angewendet (Log-Zeile "Wende Migration an: 0020_session_user_ended_index.sql"),PRAGMA foreign_key_check(Teil der Migrations-Pipeline, haelt bei Verletzung hart an) lief in keinem Lauf an. Stufe F2 hat null neue Testfehlschlaege verursacht.
3) Stufe F3 -- FERTIG (Arbeitsflaeche /workspace, eine Sitzung gleichzeitig)
Ziel (F.3, F.4-Tabelle): dauerhafte Arbeitsflaeche mit Seitenleiste; diese Stufe haelt bewusst nur EINE verbundene Sitzung gleichzeitig (F4 baut "mehrere gleichzeitig, Umschalten" darauf auf, siehe F.4-Stufentabelle).
Umgesetzt
app/main.py:GET /workspace-- wie/dashboard//adminreines statisches Markup, Auth clientseitig ueberGET /auth/me.templates/workspace.html: Topbar (Benutzername, Link zurueck zum Dashboard, Abmelden) + zweispaltiges Layout (Seitenleiste/Hauptbereich). Bindetterminal.jsUNDrdp.jsgleichzeitig ein -- deren Bootstrap-Bloecke (siehe F1) bleiben hier wirkungslos, da kein Element mit IDsession-containerexistiert;workspace.jserzeugt Instanzen ausschliesslich selbst. Enthaelt einen staendig sichtbaren Hinweis in der Seitenleiste, dass ein Reload jede offene Sitzung beendet (F.3.7 / Betreiberentscheidung #8 -- klar beschriftet statt verschwiegen, siehe Abschnitt 0 Punkt 3). Bewusst KEINbeforeunload-Bestaetigungsdialog (native Browser-Warnung) zusaetzlich eingebaut -- der Dauerhinweis in der Seitenleiste wurde als ausreichend fuer "klar beschriftet" bewertet; ein Reload-Warndialog waere eine zusaetzliche UX-Entscheidung, die die Spec nicht verlangt und die eine kuenftige Session bei Bedarf ergaenzen kann.static/js/workspace.js: orchestriert drei Zustaende im Hauptbereich (Hostkatalog / eine aktive Sitzung / Wechsel-Bestaetigung) und die Seitenleiste (Kacheln). Kernentscheidungen:- Kein Server-seitiges "welche Sitzung bin ich"-Signal noetig: die
aktive Kachel im eigenen Tab wird direkt aus dem lokalen
SshTerminalSession/RdpSession-Objekt gespeist (Status-Callback aus F1, Klick-Handler ruft direktdispose()) -- kein Abgleich mit der server-seitigensession_idnoetig, da terminal_ws.py/ws_tunnel.py diese ID bislang nirgends an den Client zurueckmelden (kein Frame dafuer im Protokoll). Bewusste Vereinfachung, dokumentiert als Limitierung unten. GET /catalog/sessions(F2) wird NUR beim Laden der Seite und direkt nach dem Beenden der eigenen Sitzung abgefragt, nicht fortlaufend waehrend eine eigene Sitzung aktiv ist. Vermeidet dadurch das Problem, die eigene (im DOM bereits als "aktive Kachel" dargestellte) Sitzung von denselben Zeilen in der API-Antwort unterscheiden zu muessen (kein Duplikat-Risiko an den beiden Abfragezeitpunkten, da die eigene Sitzung dort entweder noch nicht existiert oder bereits beendet ist). Kehrseite (dokumentierte Limitierung): Sitzungen, die WAEHREND eine eigene Sitzung offen ist in einem ANDEREN Tab/Fenster geoeffnet oder beendet werden, erscheinen/verschwinden in dieser Seitenleiste nicht live -- erst nach dem naechsten Laden/Beenden. Kein Polling in dieser Stufe eingebaut (bewusst, um die Sitzungs-Identitaets-Frage nicht ungeloest mitzuschleppen); ein spaeterer periodischer Abgleich (mit echtem Sitzungs-ID-Abgleich, s.u.) ist Backlog fuer F4/F5.- Sitzungswechsel ist ehrlich, nicht still: ein Klick auf "Verbinden"
bei bereits offener eigener Sitzung fuehrt NICHT automatisch zum
Trennen -- ein eigenes Bestaetigungspanel (
.banner.error+ Beenden-und-wechseln/Abbrechen, kein nativesconfirm()-- passt zum bestehenden Stil des Projekts, das durchgehend eigene Overlays statt Browser-Dialoge nutzt) erklaert, dass diese Stufe nur eine gleichzeitige Sitzung haelt. ?host=<id>(vondashboard.jsgesetzt) verbindet beim Laden direkt, ohne den Katalog zwischenzuschalten.
- Kein Server-seitiges "welche Sitzung bin ich"-Signal noetig: die
aktive Kachel im eigenen Tab wird direkt aus dem lokalen
static/js/dashboard.js: "Verbinden" fuehrt jetzt zu/workspace?host={id}statt direkt zu/terminal/{id}//rdp/{id}. Weiterhintarget="_blank"(eigener Tab) -- das Dashboard bleibt dadurch erreichbar, waehrend die Arbeitsflaeche laeuft; von dort fuehrt der Topbar-Link "Dashboard" zurueck. Die duennen Rahmen/terminal/{id},/rdp/{id}selbst bleiben unveraendert erreichbar (F.2: Lesezeichen/ Fehlersuche).static/css/app.css: neue.workspace-*-Regeln (Seitenleiste, Kacheln, Statuspunkt-Farben per Klasse, Hauptbereich). Wichtig:.workspace-main .session-instance .session-shell { height: 100%; }ueberschreibt dieheight: 100vh-Regel aus F1 (dort fuer den Ein-Instanz-Vollseiten-Fall gedacht) -- sonst wuerde die Sitzung in der Arbeitsflaeche die Seitenleiste ueberdecken.- Bewusst NICHT in F3 uebernommen (gehoert nicht zum Katalog-Funktions-
umfang von F.3): die "Zugangsdaten anzeigen"-Funktion des Dashboards
(
showCredentials()indashboard.js) wurde NICHT in den Arbeitsflaechen-Hostkatalog uebernommen -- bleibt eine reine Dashboard-Funktion. Spart Komplexitaet, ohne eine F.3-Anforderung zu verfehlen (die verlangt nur, dass "Neue Sitzung" den Hostkatalog zum Verbinden zeigt).
Bekannte Limitierung (fuer F4/F5 vorzumerken)
Der Server meldet die von ihm vergebene session_id (SSH: terminal_ws.py,
RDP: ws_tunnel.py) aktuell an keiner Stelle an den Client zurueck -- weder
per JSON-Frame (SSH-Protokoll koennte das leicht ergaenzen) noch per
Guacamole-Instruktion (RDP-Protokoll ist reines Guacamole-Binaerprotokoll,
eine eigene Instruktion dort einzuschleusen waere riskanter, da
guacamole-common-js nur gueltige Guacamole-Instruktionen erwartet). F3 kommt
bewusst ohne diese ID aus (s.o.). Sollte F4/F5 einen echten Abgleich
"welche Kachel entspricht welcher Server-Sitzung" brauchen (z.B. fuer
Live-Polling waehrend mehrerer offener Sitzungen), muss zuerst entschieden
werden, ob/wie die SSH- und RDP-Wege dafuer je einen zusaetzlichen
Mechanismus bekommen -- Betreiberentscheidung/Design-Frage fuer eine
kuenftige Session, nicht in dieser Stufe geloest.
Verifikation
-
node --checkfuerworkspace.js-- fehlerfrei. -
Kein manueller Browser-/SSH-/RDP-Rauchtest moeglich (siehe bereits bei F1 dokumentierte Einschraenkung -- keine echte SSH-/RDP-Zielumgebung in dieser Sandbox). Die Arbeitsflaeche selbst (Seitenleiste, Katalogwechsel, Wechsel-Bestaetigung) wurde NICHT im echten Browser gesehen -- nur durch Codelesen/CSP-Tests/Backend-Tests abgesichert. Empfehlung unveraendert: vor dem Weiterbau zu F4 einen manuellen Rauchtest mit echtem SSH-/RDP-Ziel durchfuehren.
-
tests/test_teil_f_schritt3.py(2 Tests, neu):GET /workspaceliefert die erwarteten Bausteine (Seitenleiste, Hauptbereich, Neue- Sitzung-Knopf, terminal.js/rdp.js/workspace.js eingebunden, KEIN#session-container-- sonst wuerde der F1-Bootstrap-Block automatisch eine Sitzung starten);/catalog/hostsund/catalog/sessionsbleiben fuer einen frisch angemeldeten Benutzer erreichbar (leere Sitzungsliste). -
tests/test_csp_compliance.py:/workspacein dietest_rendered_pages_contain_no_inline_style_or_script-Parametrisierung aufgenommen (jetzt 11 statt 10 Tests in der Datei) -- kein Inline- style/-script auf der neuen Seite. -
Voller Regressionslauf ueber alle 22 Testdateien (20 aus F1+F2 + der neue F3-Testfile,
test_csp_compliance.pymit einem zusaetzlichen Parametrisierungsfall):Gruppe Ergebnis admin_crud/admin_groups_tokens/audit_chain/auth_flow/phase9 36 passed, 1 failed passwords_totp/pentest_security/phase10/phase11 37 passed, 2 failed phase12/phase13 22 passed, 6 failed phase14/phase15 12 passed, 7 failed rbac/teil_d_schritt5/teil_d_schritt6/teil_f_schritt2/session_recorder 20 passed, 0 failed session_reaper 1 passed, 2 failed csp_compliance (11) + teil_f_schritt3 (2, neu) 13 passed, 0 failed Summe 141 passed, 18 failed, 159 total 159 = 156 (Stand nach F2) + 2 neue F3-Tests + 1 neuer Parametrisierungsfall in
test_csp_compliance.py. Die 18 Fehlschlaege sind wieder unveraendert dieselben wie nach F1/F2. Stufe F3 hat null neue Testfehlschlaege verursacht.
4) Stufe F4 -- FERTIG (mehrere gleichzeitige Sitzungen, Umschalten, Fokus-/Tastaturtrennung)
Ziel (F.3.2, F.3.3): mehrere Sitzungen gleichzeitig offen halten, zwischen ihnen umschalten ohne Verbindungsabbruch, Eingaben landen nur in der sichtbaren Sitzung, Groesse stimmt nach jedem Umschalten.
Geloeste Design-Frage aus F3 (Server-Sitzungs-ID-Zuordnung)
F3 hatte offen gelassen, wie eine Kachel im Browser einer server-seitigen
sessions-Zeile zugeordnet wird, da terminal_ws.py/ws_tunnel.py die
session_id an keiner Stelle an den Client zurueckmelden. Entscheidung
dieser Stufe: kein Backend-Protokoll-Eingriff (ein SSH-JSON-Frame waere
zwar risikoarm gewesen, aber eine passende Aenderung am RDP-Weg haette eine
eigene Instruktion in den Guacamole-Binaerprotokoll-Tunnel einschleusen
muessen -- ohne echten Browser/RDP-Testaufbau in dieser Sandbox als zu
riskant bewertet, um es ungetestet zu aendern). Stattdessen: static/js/ workspace.js::claimServerSessionId() -- nach instance.connect() ruft
jede lokal geoeffnete Sitzung bis zu 5x (250ms Abstand) GET /catalog/sessions?active_only=true ab und beansprucht die juengste,
Host-ID-passende, noch nicht beanspruchte Zeile fuer sich
(claimedSessionIds-Set). Damit werden eigene Kacheln korrekt aus der
"laeuft anderswo"-Liste herausgefiltert -- das Duplikat-/Verwechslungs-
Risiko, das F3 noch offen liess, ist damit fuer den Normalfall geloest.
Dokumentiertes Restrisiko: ein echter Wettlauf (zwei Tabs oeffnen fast
zeitgleich eine Sitzung zu genau demselben Host) koennte die Zuordnung in
seltenen Faellen vertauschen -- rein kosmetisch (betrifft nur, welche
Kachel als "eigene" vs. "anderswo" angezeigt wird, nicht Zugriffsrechte:
Beenden bleibt ueber POST /catalog/sessions/{id}/terminate, Stufe F2,
weiterhin hart auf den eigenen Benutzer beschraenkt, unabhaengig von dieser
Heuristik).
Umgesetzt
static/js/workspace.jskomplett auf Mehrfach-Sitzungen umgebaut:openSessions-Array statt einer einzelnenactiveSession-Variable; jede Instanz bleibt nach dem Erzeugen dauerhaft in#workspace-sessions-areaim DOM (wird nie durch Umschalten entfernt, nur ein-/ausgeblendet) -- erstdispose()(Sitzung schliessen) entfernt sie wirklich.showSession(uid): blendet alle anderen Instanzen aus (.hidden-Klasse, app.css, derselbe CSP-konforme Sichtbarkeits-Toggle wie ueberall im Projekt) und die Zielinstanz ein, ruftsetActive(true)auf ihr undsetActive(false)auf allen anderen -- genau EIN_active=truegleichzeitig.showCatalog(): blendet ALLE Sitzungen aus (setActive(false)auf jede), zeigt den Hostkatalog -- Sitzungen bleiben dabei voll verbunden im Hintergrund (Betreiberentscheidung #7).- Der bisherige F3-"Wechsel-Bestaetigung"-Dialog (eine Sitzung ersetzt die andere) entfaellt ersatzlos -- F4 erlaubt echte Koexistenz, "Verbinden" im Katalog oeffnet jetzt einfach eine zusaetzliche Kachel.
- Sidebar-Kacheln unterscheiden jetzt drei Zustaende optisch:
workspace-tile-active(gerade sichtbar),workspace-tile-background(offen, verbunden, aber ausgeblendet -- anklickbar zum Umschalten, inkl. Tastatur-Bedienung uebertabindex/Enter/Leertaste),workspace-tile-remote(laeuft in einem anderen Tab, unveraendert aus F3).
static/js/rdp.js(F.3.2, Fokus-/Tastatursicherheit):setActive(active)ruft jetztthis.keyboard.reset()(guacamole-common-js-API, dieselbe Methode, die der offizielle Client bei Fensterfokusverlust nutzt -- per Feature-Checktypeof ... === "function"abgesichert) sowohl beim Deaktivieren als auch defensiv beim Aktivieren auf, damit keine haengengebliebenen Modifiertasten (Strg/Alt) von einer verlassenen Sitzung in die naechste durchschlagen. Die eigentliche Weiterleitungs-Sperre (this._active-Gate inkeyboard.onkeydown/onkeyup/dempaste-Listener) stammt bereits aus F1 und musste hier nicht mehr geaendert werden -- sie greift jetzt einfach echt, weil erstmals mehrereRdpSession-Instanzen gleichzeitig existieren koennen.static/js/rdp.jsundstatic/js/terminal.js(F.3.3, Refit beim Sichtbarwerden): der dokumentweiteresize/fullscreenchange-Listener jeder Instanz reagiert jetzt NUR noch, wennthis._activewahr ist -- vorher haette ein Browser-Resize bei mehreren gleichzeitig existierenden, aber ausgeblendeten (display:none, Groesse 0) InstanzenfitAddon.fit()bzw.sendSize()mit bedeutungslosen Nullgroessen ausgeloest. Der eigentliche Refit beim Umschalten passiert weiterhin explizit insetActive(true)(unveraendert aus F1:sendResize()bzw._sendCurrentSize()).templates/workspace.html:#workspace-mainin zwei feste Unterbereiche aufgeteilt --#workspace-catalog-viewund#workspace-sessions-area-- statt vorherinnerHTMLbei jedem Zustandswechsel neu zu schreiben (das haette in F4 alle offenen Sitzungs-Elemente mitsamt ihrer aktiven WebSockets zerstoert).static/css/app.css:.workspace-tile-background/-remote-Stile;.workspace-sessions-area .session-instance(statt vorher direkt auf.session-instanceim Hauptbereich) fuer die Hoehe; der F3-Uebergangsstil fuer die Wechsel-Bestaetigung (.workspace-replace-confirmusw.) entfernt, da der Dialog entfaellt.
Bewusst NICHT in Stufe F4 (folgt in F5)
- F.3.8 (Obergrenze in der Oberflaeche sichtbar -- "Neue Sitzung"
deaktivieren) -- ein Ueberschreiten zeigt sich in F4 nur reaktiv als
Fehlerstatus auf der neuen Kachel (Server lehnt mit WS-Code 4429 ab, siehe
app/ssh_proxy/terminal_ws.py/app/rdp_proxy/ws_tunnel.py, E.4 aus Teil E) -- funktional korrekt, aber nicht die von F.3.8 verlangte vorherige Kennzeichnung. - Live-Aktualisierung der Seitenleiste fuer Sitzungen, die in ANDEREN Tabs waehrend einer laufenden eigenen Sitzung geoeffnet/beendet werden (siehe oben, unveraendert aus F3 -- die Sitzungs-ID-Zuordnung loest das Duplikat-Problem, aber es wird weiterhin nicht periodisch neu abgefragt).
Verifikation
-
node --checkfuerterminal.js,rdp.js,workspace.js,dashboard.js-- alle vier fehlerfrei. -
Weiterhin kein manueller Browser-/SSH-/RDP-Rauchtest moeglich (keine echte Zielumgebung in dieser Sandbox). Das betrifft F4 staerker als F1-F3, weil hier die eigentliche, in der F.5-Risikotabelle als groesstes Risiko benannte Eigenschaft entsteht ("Eingaben landen in der falschen Sitzung"). Die Absicherung stuetzt sich in dieser Stufe ausschliesslich auf Codelesen/-gegenueberstellung (SSH:
xterm.jsbindet ohnehin an sein eigenes Containerelement, kein Aenderungsbedarf; RDP: das bereits in F1 angelegte_active-Gate plus das neuekeyboard.reset()) -- dringende Empfehlung fuer die naechste Session mit Zugriff auf echte SSH-/RDP-Ziele: das F.6-Abnahmeszenario (vier Sitzungen, mehrfach umschalten, 30 Minuten offen lassen) tatsaechlich im Browser durchspielen, bevor F5 als "fertig" gilt. -
Keine neuen Backend-Testdateien in dieser Stufe (F4 ist eine reine Frontend-/JS-Aenderung ohne neue Endpunkte, Migrationen oder Schema-Aenderungen -- die bestehenden F1-F3-Tests decken weiterhin denselben Backend-Vertrag ab, auf dem
workspace.jsaufbaut). Voller Regressionslauf ueber alle 22 Testdateien (unveraendert gegenueber F3):Gruppe Ergebnis csp_compliance/teil_f_schritt2/teil_f_schritt3 17 passed, 0 failed admin_crud/admin_groups_tokens/audit_chain/auth_flow/phase9 36 passed, 1 failed passwords_totp/pentest_security/phase10/phase11 37 passed, 2 failed phase12/phase13 22 passed, 6 failed phase14/phase15 12 passed, 7 failed rbac/teil_d_schritt5/teil_d_schritt6/session_recorder 16 passed, 0 failed session_reaper 1 passed, 2 failed Summe 141 passed, 18 failed, 159 total Identische Zahlen wie am Ende von F3 (159 Tests insgesamt, dieselben 18 bereits bekannten Fehlschlaege) -- erwartungsgemaess, da F4 keine Backend-/Testdatei-Aenderungen enthielt. Stufe F4 hat null neue Testfehlschlaege verursacht, wohlgemerkt bei einer Stufe, deren eigentliche Kernaussage (Eingabe-Isolation) mit den vorhandenen Backend-Tests strukturell gar nicht pruefbar ist -- siehe Rauchtest-Empfehlung oben.
5) Stufe F5 -- FERTIG
Ziel (F.4 der Spec-Tabelle, Cowork-Task #21): Obergrenzen in der Oberflaeche sichtbar machen (F.3.8), Sammelzustand-Kennzeichnung bei Hintergrundfehlern/-trennungen, Feinschliff.
F.3.8 -- Obergrenzen sichtbar
Bisher (F1-F4) war die Durchsetzung der Obergrenzen (Teil E.4,
app/ssh_proxy/terminal_ws.py/app/rdp_proxy/ws_tunnel.py, VOR jedem
Ressourcenverbrauch geprueft, WS-Code 4429 bei Ueberschreitung) bereits
vollstaendig vorhanden und wirksam -- F.3.8 verlangt zusaetzlich, dass die
Grenze VOR dem Verbindungsversuch sichtbar ist, nicht erst als
Fehlerstatus danach.
app/catalog/routes.py: neuer EndpunktGET /catalog/session-limits(Abschnitt "Sitzungs-Obergrenzen (F5, Teil F.3.8)"). Liest ausschliesslich bereits vorhandene Quellen --app/config.py(max_sessions_per_user,max_sessions_global) undapp/security/active_sessions.py(count_for_user(),count_total()) -- und bildet exakt dieselbe Schwellenlogik wie die Durchsetzung nach: Admins sind von der Je-Nutzer-Grenze ausgenommen (user_limit_applies/at_user_limitnur beinot user.is_admin), nicht aber von der globalen Grenze (at_global_limitgilt fuer jeden). Absichtlich keine neue Zustandsmaschine, keine Verdopplung der Durchsetzung -- reine Lesefunktion. Liefert ausschliesslich aggregierte Zahlen, keine Details zu fremden Sitzungen (user_id/hostname anderer Nutzer bleiben wie beim gesamten Katalog-Router unsichtbar, siehe Moduldoc).static/js/workspace.js:refreshSessionLimits()/atAnyLimit()/renderLimitHint()-- ruft den neuen Endpunkt beim Laden (main()), nach dem Start einer Sitzung und nach dem Schliessen (jeweils mit derselben 300ms-Verzoegerung wie der bestehenderefreshRemoteSessions()-Aufruf, daactive_sessions.register()/unregister()serverseitig erst nach dem WS-Handshake bzw. nach demfinally-Block laufen).renderLimitHint()zeigt "Eigene Sitzungen: X/Y" (nur wenn die Grenze fuer diesen Nutzer gilt) und "Server gesamt: X/Y" im Sidebar-Header und deaktiviert+ Neue Sitzung, sobald eine der beiden Grenzen erreicht ist.hostCard()deaktiviert zusaetzlich jeden einzelnen "Verbinden"-Knopf im Hostkatalog unter derselben Bedingung. Serverseitige Durchsetzung (WS-Code 4429) bleibt unveraendert die verbindliche Kontrolle -- diese Anzeige ist bewusst nur informativ und kann durch die Race zwischen Anzeige-Refresh und tatsaechlichem Verbindungsaufbau kurzzeitig veralten (dieselbe Klasse von Eventual-Consistency wie beiclaimServerSessionIdaus F4).templates/workspace.html: neues Element<p id="workspace-limit-hint">im Sidebar-Header.static/css/app.css:.workspace-limit-hint--at-limit(Warnfarbe bei erreichter Grenze) sowie ein neues globalesbutton:disabled { opacity: 0.5; cursor: not-allowed; }(gab es vorher nicht -- deaktivierte Knoepfe sahen bislang optisch aktiv aus).
Sammelzustand-Kennzeichnung bei Hintergrundfehlern/-trennungen
static/js/workspace.js:hasAttention(record)(Hilfsfunktion nebenstatusClass()) erkennt eine Hintergrundsitzung mit Fehler- oder Trennungsstatus.buildLocalTile()markiert eine solche Kachel zusaetzlich mit der Klasseworkspace-tile-attentionund ersetzt den generischen Text "im Hintergrund verbunden" durch "im Hintergrund: Fehler" bzw. "im Hintergrund: getrennt".renderSidebar()fasst zusaetzlich alle betroffenen Hintergrundsitzungen (die gerade aktive zaehlt nicht mit, ihr Zustand ist ohnehin direkt sichtbar) in einer Sammel-Zeile ueber der Kachelliste zusammen ("⚠ N Sitzungen im Hintergrund mit Problem"), damit ein Problem nicht erst durch Umschalten auf jede einzelne Kachel entdeckt wird.templates/workspace.html: neues Element<div id="workspace-attention-summary" class="hidden">.static/css/app.css:.workspace-tile-attention(roter Rahmen) und.workspace-attention-summary(Warnbanner-Stil).
Feinschliff
- Globales
button:disabled-Stil (siehe oben) -- kam durch F.3.8 zum ersten Mal ueberhaupt im Projekt zum Einsatz (vorher gab es keine deaktivierten Knoepfe in dieser Form), war aber vorher schlicht nicht vorhanden und wird jetzt konsequent von allen drei neuen deaktivierbaren Knoepfen (+ Neue Sitzung, jeder Katalog-"Verbinden") genutzt.
Bewusst NICHT in Stufe F5
- Keine Aenderung an der serverseitigen Durchsetzung selbst (E.4) -- F5 ist ausschliesslich Anzeige/UX auf Basis der bestehenden, bereits in frueheren Sessions verifizierten Logik.
- Keine Push-/Live-Aktualisierung der Obergrenzen-Anzeige zwischen Tabs (dieselbe bekannte Einschraenkung wie die Sitzungsliste selbst seit F3 -- kein Polling, nur bei den bereits vorhandenen Refresh-Zeitpunkten dieser Seite).
Verifikation
-
node --checkfuerworkspace.js(auf dem Geraet) -- fehlerfrei.python3 -m py_compilefuerapp/catalog/routes.py-- fehlerfrei. -
Neue Testdatei
tests/test_teil_f_schritt5.py(5 Tests, alle bestehen): Obergrenzen entsprechenapp/config.pybei leerer Registry;current_user_countzaehlt ausschliesslich eigene Registry-Eintraege (fremde Sitzung beeinflusst NUR den globalen, nicht den eigenen Zaehler -- F.5-Risikotabelle-Prinzip wie intest_teil_f_schritt2.py);at_user_limitwird bei Erreichen vonmax_sessions_per_userwahr; die Admin-Asymmetrie austerminal_ws.py(ausgenommen von der Je-Nutzer-Grenze, NICHT von der globalen) wird exakt nachgebildet; der Endpunkt verlangt eine Anmeldung (401 sonst). -
Voller Regressionslauf ueber alle 23 Testdateien:
Gruppe Ergebnis csp_compliance/teil_f_schritt2/teil_f_schritt3/teil_f_schritt5 22 passed, 0 failed admin_crud/admin_groups_tokens/audit_chain/auth_flow/phase9 36 passed, 1 failed passwords_totp/pentest_security/phase10/phase11 37 passed, 2 failed phase12/phase13 22 passed, 6 failed phase14/phase15 12 passed, 7 failed rbac/teil_d_schritt5/teil_d_schritt6/session_recorder 16 passed, 0 failed session_reaper 1 passed, 2 failed Summe 146 passed, 18 failed, 164 total Gegenueber F4 (141/18/159): +5 neue, bestehende Tests, dieselben 18 bereits bekannten, dokumentierten Fehlschlaege (siehe
FORTSETZUNG_Teil_C.md), null neue Regressionen.
6) Abnahme (F.6) -- Code-Stand fertig, Live-Szenario ausstehend
Wortlaut F.6 (Umsetzungsauftrag_Sonnet5.md): "Ein Benutzer oeffnet vier
Sitzungen (zwei SSH, zwei RDP) in der Arbeitsflaeche, schaltet mehrfach
zwischen ihnen um und laesst sie 30 Minuten offen. Danach gilt: alle vier
sind noch verbunden, in jeder ist die zuletzt gesehene Ausgabe vollstaendig
vorhanden, Eingaben landen ausschliesslich in der sichtbaren Sitzung, die
Groesse stimmt nach jedem Umschalten, es existieren genau vier
sessions-Zeilen mit vier Aufzeichnungen, und ein zweiter Benutzer arbeitet
waehrenddessen unbeeintraechtigt."
Beim Vorbereiten dieses Abschnitts gefundene und behobene Luecke
Bei der Pruefung von "30 Minuten offen, alle vier noch verbunden" gegen den
tatsaechlichen Code fiel auf: F.3.4 ("Hintergrundsitzungen duerfen nicht
sterben") war fuer eine echt untaetige SSH-Sitzung NICHT vollstaendig
erfuellt. app/config.py::ssh_idle_timeout_s steht auf 900s (15 Minuten);
E10 (app/ssh_proxy/terminal_ws.py) zaehlt zwar sowohl Eingabe als auch
Zielausgabe als Aktivitaet (deckt z.B. tail -f/einen laufenden Build ab),
aber eine Sitzung mit einer wirklich stillen Shell (kein Tastendruck, keine
spontane Ausgabe) haette nach 15 Minuten den Idle-Timeout ausgeloest --
still im Hintergrund, waehrend die Kachel in der Seitenleiste weiter
"verbunden" zeigte. Das widerspricht direkt Betreiberentscheidung #7
("voll verbunden bleiben") und haette das F.6-Kriterium selbst beim
einfachsten denkbaren Testlauf (vier Sitzungen oeffnen, nichts weiter tun,
30 Minuten warten) zum Scheitern gebracht. RDP (app/rdp_proxy/ws_tunnel.py)
hat ueberhaupt keinen Idle-Timeout, ist also nicht betroffen.
Behoben rein clientseitig, ohne die serverseitige Logik zu aendern:
static/js/terminal.js::SshTerminalSession.connect() sendet ab jetzt alle
4 Minuten (deutlich unter den 15 Minuten) einen inhaltsleeren
{"type": "ping"} ueber die offene WebSocket-Verbindung, unabhaengig davon,
ob die Sitzung gerade sichtbar ist oder im Hintergrund liegt. Das ist keine
neue Sonderbehandlung auf dem Server: terminal_ws.py aktualisiert
activity["t"] bereits fuer JEDE empfangene Nachricht, noch VOR der
type-Weiche (if msg.get("type") == "input": ... elif ... == "resize": ...) -- ein unbekannter type wie "ping" faellt danach einfach durch,
ohne Fehler und ohne dass eine der beiden Zweige ausgefuehrt wird. Timer
wird in dispose() und im close-Handler aufgeraeumt (kein Leck bei
Sitzungsende). node --check fehlerfrei; der volle Regressionslauf
(csp_compliance/teil_f_schritt2/3/5, siehe unten) zeigt weiterhin 22/22 --
erwartungsgemaess, da die Aenderung rein clientseitig ist und die
Server-Verzweigung fuer unbekannte type-Werte bereits vor dieser Aenderung
bestand (kein neuer, testbar isolierter Serverpfad entstanden).
Kein Backend-Test fuer diese Aenderung: die Idle-Timeout-Schleife liegt
inline in der grossen ssh_terminal()-Route (nicht in einer eigenstaendigen
Funktion) und haengt an einer echten asyncssh-Verbindung -- ein
Zeit-basierter WS-Integrationstest dafuer waere ein eigenes, nicht triviales
Vorhaben (aehnliche Komplexitaet wie die bereits bestehenden, teils
fehlschlagenden Mock-Tests in test_phase15.py) und wurde hier bewusst
nicht nachgezogen, um die Codepruefung nicht mit einem unsicheren neuen Test
zu vermischen. Die Absicherung stuetzt sich auf Codelesen (oben) --
die eigentliche Bestaetigung braucht den echten 30-Minuten-Lauf.
Kriterium-fuer-Kriterium-Stand
| Kriterium (F.6) | Stand |
|---|---|
| Vier Sitzungen (2 SSH + 2 RDP) gleichzeitig oeffnen | Code vorhanden (F4: mehrere gleichzeitige SshTerminalSession/RdpSession-Instanzen, keine Zerstoerung beim Umschalten); Standard-Obergrenze 6/Nutzer laesst vier zu. Live-Test aussteht. |
| Mehrfaches Umschalten | Code vorhanden (F1/F4: setActive(), .hidden-Toggle, Refit/sendSize() beim Sichtbarwerden). Live-Test aussteht. |
| 30 Minuten offen, danach alle vier noch verbunden | Betreiberentscheidung #7 umgesetzt; SSH-Idle-Timeout-Luecke oben gefunden UND behoben; RDP ohne Idle-Timeout unproblematisch. Live-Test aussteht (einzige Moeglichkeit, echte 15+ Minuten Stille zuverlaessig zu pruefen). |
| Ausgabe in jeder Sitzung vollstaendig vorhanden | Kein Datenverlust durch die Architektur zu erwarten (jede Instanz haelt ihren eigenen xterm.js/Guacamole-Zustand, wird nie zerstoert) -- aber nur am echten Terminal pruefbar. Live-Test aussteht. |
| Eingaben nur in der sichtbaren Sitzung | Code vorhanden: SSH bindet ohnehin an sein eigenes Containerelement (kein Aenderungsbedarf); RDP nutzt das _active-Gate (F1) plus keyboard.reset() beim Umschalten (F4). Laut F.5-Risikotabelle das groesste Einzelrisiko der ganzen Stufe. Live-Test aussteht -- dringendste Einzelpruefung vor einer echten Abnahme. |
| Groesse stimmt nach jedem Umschalten | Code vorhanden: setActive(true) loest sendResize()/_sendCurrentSize() aus (F1), Hintergrund-Resize-Events sind seit F4 per _active-Gate unterdrueckt. Live-Test aussteht. |
Genau vier sessions-Zeilen mit vier Aufzeichnungen |
Jede Sitzung erzeugt beim Verbindungsaufbau ihre eigene DB-Zeile + SessionRecorder (unveraendert seit vor Teil F, F.3.10 als harte Regel nie angetastet) -- vier gleichzeitige Instanzen ergeben strukturell vier Zeilen. Live-Test aussteht (Stichprobe in der DB nach dem Lauf). |
| Zweiter Benutzer unbeeintraechtigt | Jede neue Katalog-/Sitzungs-API in F2/F5 ist zwingend auf user_id = eigene beschraenkt (siehe Moduldoc app/catalog/routes.py, dedizierte Tests in test_teil_f_schritt2.py/test_teil_f_schritt5.py fuer Fremdsitzungs-Isolation). active_sessions-Registry ist bereits seit Teil E pro Sitzung, nicht pro Nutzer, geteilt. Live-Test aussteht (echter zweiter Login parallel). |
Ehrliches Fazit
Der komplette Code-Stand fuer Teil F (F1-F5) ist fertig, regressionsfrei
(146 bestehende + 5 neue Tests bestehen, dieselben 18 laengst bekannten,
dokumentierten Fehlschlaege aus FORTSETZUNG_Teil_C.md unveraendert) und
gegen jedes einzelne F.6-Kriterium durchgesehen -- dabei wurde eine echte,
bis dahin unentdeckte Luecke gefunden und behoben (SSH-Idle-Timeout fuer
Hintergrundsitzungen, oben). Was in dieser Sandbox nicht moeglich war
und fuer keine Stufe von F1 bis F5 nachgeholt werden konnte: ein
echter Browser mit echten SSH-/RDP-Zielsystemen. Alle sieben Kriterien in
der Tabelle oben sind auf Code-Ebene plausibel, aber keines ist tatsaechlich
durchgespielt worden. Teil F sollte erst dann als im Sinne von F.6
"abgenommen" gelten, wenn eine Session mit Zugriff auf echte
SSH-/RDP-Testziele und einen echten Browser das Szenario tatsaechlich einmal
durchspielt -- mit besonderem Augenmerk auf Eingabe-Isolation (groesstes
Risiko laut F.5) und den 30-Minuten-Idle-Fall (siehe oben).