# Session Restore V2 État de cette révision : **deux P0 de frontière prewarm/finalisation corrigés ; validation privée Sway 1.12/Pixman ; login physique non testé pour ce patch**. Le prewarm antérieur a été validé sur SwayFX 0.6 headless ; cette preuve historique ne valide pas automatiquement le présent delta. Lors du premier login du 17 octobre 2026, l’ancienne séquence a échoué avant le premier swap avec `FOCUS_RECOVERY_NOT_PROVEN` : les trois fenêtres ont été remises à leur place manuellement et l’incident archivé. La correction actuelle prépare les applications manquantes avant la transaction et restaure le focus final. Son efficacité lors d’un prochain login physique reste à vérifier. La recette réelle réussie du **7 octobre 2026** est une preuve historique de la révision d’alors : logout, checkpoint V2, arrêt UWSM, login, chooser unique, arbre, focus final et identité Firefox exacte vérifiés. Elle ne valide pas les changements de focus actuels ni le prochain login physique. Les preuves historiques restent dans la branche locale de recherche. ## Utilisation Le menu power conserve Verrouiller, Suspendre, Déconnexion, Redémarrer et Éteindre. Déconnexion sauvegarde un checkpoint V2 avant `uwsm stop` ; Redémarrer et Éteindre sauvegardent avant leur action systemd. Un échec de checkpoint permet d’annuler ou de choisir explicitement de quitter sans sauvegarder. Au login suivant, le chooser propose Restaurer ou Nouvelle session. Un clic Restaurer retire sa surface et attend deux observations QML de ce retrait (délai maximal de 1,5 seconde). Sous le verrou Session V2, le backend refuse d’abord une récupération antérieure inachevée, puis **prépare les applications absentes via le catalogue**, avant de déplacer une fenêtre ou de publier un journal Recovery/Attempt. Limusic appartient exclusivement à l’autostart Sway : elle n’est jamais lancée par Session V2, et son absence bloque la transaction. Lorsque toutes les fenêtres attendues sont présentes, le backend vérifie les identités, emplacements et cardinalités, puis exige une preuve stable du focus Sway et de l’identité de session avant de reconstruire le layout. Un focus inconnu ou ambigu (`con_id=null, workspace=null`) reste refusé sans feuille inventée. Un focus explicitement situé sur un workspace, même sans feuille, est une observation valide, sans preuve de la provenance d’une activation ultérieure. Les changements de focus pendant la transaction restent refusés. Après `DEFERRED` pour absence de focus, le dialogue reste sans capture clavier : **activez vous-même une fenêtre ou un espace avec vos raccourcis Sway, puis cliquez Réessayer**. Si la préparation n’a pas ouvert une application, le dialogue conserve les fenêtres déjà lancées et propose également **Réessayer**, sans les lancer une deuxième fois. Le clic demande une nouvelle preuve ; il n’autorise pas un re-focus arbitraire. Aucun retry n’est automatique. **Nouvelle session** reste disponible. Si une autre surface exclusive conserve le clavier, il faut d’abord la libérer. Un retrait QML expiré permet également un retry explicite sans capture clavier. Le lot phase0 épingle les `con_id`, PID, `/proc` starttime, `app_id` et placements runtime. Après l’initialisation de Guard/Anchors et avant le premier move, le lot entier est relu. Chaque staging vérifie encore les membres et les placements attendus ; ses cibles viennent exclusivement du lot épinglé. Une fenêtre ajoutée ou remplacée après prewarm est refusée, y compris à `app_id` et cardinalité identiques. L’origine Recovery n’est jamais redécouverte par nom applicatif. Aucun lancement Limusic n’est ajouté. La vérification finale relit la structure **après** les appels au provider et **après** `guard.finish()`. Elle contrôle layout canonique, membership, sorties, flottement/géométrie flottante, focus verrouillé et identités runtime complètes de toutes les fenêtres gérées. Les UUID exacts du provider restent exigés. `Recovery.complete()` refuse identité ou placement divergents, ordre relatif modifié, focus pending/conflict et toute fenêtre pending. Executor lui fournit le contrôle structurel sur sa dernière observation avant publication. Une normalisation de parent unaire par Sway reste permise si placement, ordre et layout canonique sont conservés ; les ratios exacts restent hors contrat. Ces contrôles ne sont **pas un CAS** : ils ne garantissent rien contre une modification postérieure à la dernière observation du compositeur. Un succès exige le rapport final vérifié puis son acquittement durable. Après cet acquittement ou Nouvelle session, un redémarrage QuickShell dans la même session ne réaffiche pas le chooser. Un simple claim n’est pas un acquittement : après redémarrage, un état DEFERRED conserve son retry explicite, un backend encore verrouillé est observé sans relancement, et une interruption présente le diagnostic sans restauration automatique. Une intention IPC ambiguë reste `RECOVERY_REQUIRED` ; Réessayer ne contourne pas cette protection. Le gestionnaire de sessions permet sauvegarde, liste, aperçu structurel, suppression et restauration V2. L’aperçu ne modifie aucune fenêtre. Le backend fournit aussi `show` pour inspecter une session. Les noms sont limités à 64 caractères ASCII alphanumériques, tirets et underscores, commençant par un caractère alphanumérique. Ne pas publier les sessions ou rapports runtime. ## Installation reproductible Prérequis : Python 3, Sway compatible, QuickShell/Qt 6, GTK3, compilateur C17, `pkg-config`, autotiling, Kitty, Firefox et Limusic déjà configuré par l’autostart Sway. Le catalogue est `session_v2/applications.py`. Limusic utilise `strategy=managed-autostart`, `managed_by=sway-autostart` et aucun argv Labfy. Depuis la racine du dépôt : ```sh python3 -B tools/build-session-v2.py install -Dm755 build/session-v2/labfy-v2-anchor \ quickshell/.config/quickshell/labfy-sway/session_v2/libexec/labfy-v2-anchor ``` Le build ne déploie ni extension ni préférence. Le helper compilé et les archives restent ignorés par Git. Déployer le package Stow `quickshell` selon le guide habituel, puis suivre [la documentation du companion Firefox](../firefox-extension/session-v2/README.md). Pour la correction d’identité Firefox 1.1.1, les sources locales ne suffisent pas : une mise à jour XPI signée AMO unlisted puis une installation explicitement autorisée sont nécessaires. Voir le companion pour le protocole de lecture seule, l’auto-baseline uniquement après attestation native d’absence du checkpoint automatique V2 et de toute entrée de sauvegarde nommée dans `sessions` (échec fermé si contrôle inaccessible), les Ctrl+N automatiques après photographie initiale, les ambiguïtés isolées et le risque résiduel de restauration tardive indiscernable. Aucun timer ne constitue une preuve de nouveauté ni un CAS sessionstore. Les fenêtres ambiguës observées restent refusées. Ce delta n’a pas été validé au login physique et ne récupère pas un UUID déjà perdu. Versions de la recette historique du 7 octobre : Firefox 157.0, SwayFX 0.6, QuickShell 0.3.1, autotiling 1.9.4, Python 3.14.7, Qt Declarative 6.11.2 et GTK3 3.24.52. ## Architecture et invariants `sessionui/SessionV2Service.qml` est l’unique propriétaire des commandes UI. `Bar.qml` instancie `SessionStartupPromptV2` ; `shell.qml` désigne une seule barre hôte. `ControlCenter.qml` utilise `SessionManagerV2` et `SessionExitGate` avec le statut de checkpoint `saved`. Aucune surface active ne dépend du moteur V1. Le backend capture applications, slots et structure ; il reconstruit par ancres et swaps, puis vérifie arbre, absence d’ancres et focus final. Le guard possède la suspension/reprise des helpers et leur récupération en cas d’interruption. Les observations runtime ne sont pas des identités persistantes. Firefox garde la propriété de ses fenêtres/onglets ; le companion fournit l’identité exacte. Kitty restaure uniquement la fenêtre. Les échecs sont explicites ; aucune approximation d’identité Firefox n’est substituée silencieusement. Les checkpoints et sessions sont privés sous `${XDG_STATE_HOME:-$HOME/.local/state}/labfy-sway/session-v2`. Les transactions, locks, rapports et observations du provider restent sous `$XDG_RUNTIME_DIR`. Les anciennes données utilisateur V1 sont conservées. `analyze-v1-migration` est une analyse en lecture seule, sans conversion automatique ni moteur V1. Les anciens documents V1 sont des références historiques non exécutables. ## Diagnostics et validation ```sh python3 -B "$HOME/.config/quickshell/labfy-sway/session-v2.py" restore-status qs ipc -c labfy-sway call sessionV2 status systemctl --user is-active quickshell-labfy-sway.service ``` Le rapport de succès doit indiquer `status=success`, les deux vérifications finales vraies, autant d’ancres nettoyées que créées et autant de helpers repris que suspendus. Le diagnostic QuickShell doit retourner `backend=v2`, `busy=false`. Le rapport backend contient des identités privées : ne pas le joindre brut à un ticket public. Ne jamais relancer un restore pour simplement lire son statut. Tests sur compositeur headless privé, sans logout ni accès au bureau utilisateur : ```sh python3 -B quickshell/.config/quickshell/labfy-sway/tests/focus/test_focus.py python3 -B quickshell/.config/quickshell/labfy-sway/tests/focus/test_qml.py python3 -B -m unittest discover -s tests/session_v2 -p 'test_*.py' python3 -B tests/session_v2/validate_login_focus.py python3 -B tests/session_v2/runtime.py python3 -B tests/session_v2/failures.py python3 -B tests/session_v2/crashes.py python3 -B tests/session_v2/security.py python3 -B tests/session_v2/phase3b_runtime.py python3 -B tests/session_v2/phase3b_ui.py QT_QPA_PLATFORM=offscreen /usr/lib/qt6/bin/qmltestrunner \ -input tests/session_v2/qml -o -,txt ``` Les tests Python nécessitent `jsonschema` ; les scénarios UI nécessitent QuickShell et QtTest. Les résultats synthétiques restent sous `build/session-v2-tests`, ou dans `LABFY_SESSION_V2_EVIDENCE`. Les fixtures ne lancent que leur propre compositeur et leurs enfants TEST_ONLY. Le build helper C17 utilise `-Wall -Wextra -Werror`. Les métadonnées QuickShell peuvent produire des avertissements qmllint ; le chargement réel et les scénarios Qt complètent le contrôle syntaxique, sans prétendre à zéro avertissement. Validation du 17 octobre dans la copie isolée : tests backend, focus et QML, 35 scénarios runtime, erreurs contrôlées, interruptions/SIGKILL, sécurité, Limusic synthétique et scénarios QuickShell réels sur Wayland headless. Le cas `deferred-real` maintient une seconde surface QuickShell exclusive : focus Sway nul observé, refus sans journaux, aucun retry automatique, retrait explicite de la surface, activation de la fenêtre tierce par le contrôleur de test, puis demande Réessayer et succès. La fenêtre tierce retrouve son vrai focus ; Limusic synthétique reste unique. Les fonctions des boutons sont appelées par le harnais QML : ce n’est pas une recette de clic physique. Le test crash mid-command tue l’executor après mutation IPC réelle mais avant confirmation dans Recovery : l’intention durable reste pending et `RECOVERY_REQUIRED`, sans prétendre à une récupération prouvée. Les cas Limusic absente et dupliquée refusent sans lancement ni fermeture de Limusic. Le catalogue et les lignes Sway `assign [app_id="limusic-app"] workspace 10` et `exec limusic-app` restent inchangés. Dans le bac à sable Codex, `/dev/dri` était inaccessible : le SwayFX 0.6 installé n’a pas pu initialiser son renderer GLES2. Cet échec initial ne vaut pas un test fonctionnel. La première validation a donc utilisé Sway 1.12, extrait du paquet local `sway-1:1.12-4-x86_64.pkg.tar.zst` dans `/tmp`, avec Pixman, QuickShell 0.3.2 et Qt 6.12.0, sans remplacer le compositeur installé. Après réintégration contrôlée, une deuxième validation a réussi directement sur l’hôte ArchASP avec **SwayFX 0.6 installé**, en backend headless privé et avec accès au renderer de l’hôte : 35 scénarios runtime, 3 erreurs contrôlées, 10 scénarios de crash, 22 contrôles de sécurité, Limusic synthétique et 7 scénarios UI QuickShell, dont `deferred-real`. Les tests backend (27), focus (18), QML (12) et `qmllint` passent également sur les sources réintégrées. Aucun essai n’a accédé au socket Sway de la session utilisateur. **Nouvelle validation après la correction prewarm (17 octobre 2026).** Une campagne sur le SwayFX 0.6 installé, mais sur un socket Wayland/Sway IPC headless privé, a réussi les tests de focus (28), backend et prewarm (34), QML Python (13), QtTest (5) et onze scénarios composés. Le cas nominal exécute le véritable chemin de lancement Python `Popen` avec des applications GTK **synthétiques** : Limusic préexiste sur 10, Kitty et Firefox arrivent sur 1 avant toute mutation du layout ; trois swaps, trois slots, arborescence vérifiée, stage vide et focus final sur Firefox/1. Sont également vérifiés : lancement Firefox en échec puis retry sans deuxième Kitty, focus tiers préservé, mappings concurrents refusés et intentions IPC ambiguës conservées. Sur le même SwayFX headless privé, les suites runtime (35 scénarios), erreurs contrôlées (3), crash/SIGKILL (10), sécurité (22), propriété Limusic et les sept scénarios de QuickShell passent. Les sources de production validées dans la copie de test sont identiques octet pour octet à la version retenue pour cette intégration. Les tests ne lancent jamais le vrai Firefox, Kitty ou Limusic du bureau : le **prochain login réel reste non validé**, y compris l’intégration de l’extension Firefox avec son profil habituel. Voir aussi [l’investigation du premier échec](session-v2-login-focus-investigation.md). Les fixtures acceptent `LABFY_TEST_SWAY=/chemin/absolu/vers/sway` et `LABFY_TEST_RENDERER=pixman` pour une exécution sans renderer GLES2 ; pour Qt headless utiliser `QT_QPA_PLATFORM=wayland` et `QT_QUICK_BACKEND=software`. Elles imposent un backend headless, un socket privé et HOME/XDG temporaires. Le répertoire de preuves, s’il est précréé, doit avoir le mode 0700 (les JSON sont privés). Ces résultats prouvent les chemins testés sur ce compositeur headless. Ils ne prouvent ni le retour du focus avec tous les périphériques du bureau, ni le comportement du prochain login SwayFX/UWSM réel. Cette validation reste à faire séparément après réintégration contrôlée. La campagne des corrections P0, ses résultats, les refus initiaux et les commandes de reprise sur hôte sont détaillés dans [la validation prewarm privée](session-v2-prewarm-private-validation.md). Le refus DRM du SwayFX installé dans le bac à sable n’est pas un PASS SwayFX. ## Correction des chemins post-Guard du premier login Le dernier login réel fourni au diagnostic a encore refusé après intégration P0 : `TARGET_OBSERVATION_CHANGED`, zéro déplacement, zéro swap. Les journaux réels restent préservés ; ils n’ont pas été recherchés ni modifiés ici. Les chemins exacts PREWARM n’étant pas archivés, l’insertion de parents unaires par Sway/autotiling est une hypothèse forte, pas une cause historique prouvée. La contradiction démontrée est corrigée : après Guard, le lot courant déjà comparé à la phase 0 devient l’observation canonique passée aux admissions. Recovery conserve ses relectures strictes ; identité, placement, ordre et preuve de focus ne sont pas élargis. Les chemins actualisés par les commandes possédées sont acceptés après provider ; un changement externe pendant cette attente reste refusé. Les protections P0 restent en place. La campagne privée Sway 1.12/Pixman passe : 43 tests backend, suites précédentes complètes, quatre succès de quatre slots avec parents unaires (dont un avec autotiling enfant) et un refus post-provider ciblé. Le SwayFX/GLES2 du sandbox échoue au démarrage faute de DRM. **Cette correction ne valide pas le login graphique physique**, ni les applications Firefox/Kitty/Limusic réelles. Voir le [diagnostic ciblé, les preuves et la commande hôte](session-v2-path-stability-diagnostic.md). ## Limites conservées - Contenu et processus terminal non restaurés. - Ratios exacts non restaurés. - Fullscreen et scratchpad non pris en charge actuellement. - Multi-output physique non validé. - Provider Firefox candidat actuel pour le profil supporté ; plusieurs profils simultanés ne sont pas pris en charge.