248 lines
16 KiB
Markdown
248 lines
16 KiB
Markdown
# Premier login V2 — investigation isolée du 17 octobre 2026
|
|
|
|
Cette page conserve l'investigation précédente et son état BLOCKED historique.
|
|
La suite [phase 0 prewarm et focus final](session-v2-prewarm-private-validation.md)
|
|
implémente la préparation avant transaction autorisée par le nouveau ticket,
|
|
sans relaxer Recovery. La [reproduction précédente du refus naturel](session-v2-login-focus-private-validation.md)
|
|
reste disponible ; ses résultats précèdent ce changement.
|
|
|
|
## Résultat et portée
|
|
|
|
**BLOCKED pour une restauration automatique prouvée du premier login.** La cause
|
|
initiale du journal réel fourni ne peut pas être reconstruite à partir de son
|
|
observation tardive. Le patch est conservateur : il préserve le premier écart
|
|
observé, bloque aussi les mutations prédites neutres après conflit et vérifie
|
|
l'identité runtime à la réadmission. Il ne transforme pas un mapping admis en
|
|
preuve de provenance du focus. Ne pas présenter ce patch comme une résolution
|
|
du login ou le déployer automatiquement.
|
|
|
|
Travail limité à `/tmp/labfy-session-v2-real-focus-PFRt9hSW`, état Git initial
|
|
sans modification. Aucun fichier réel du journal n'a été recherché. Les données
|
|
réelles ci-dessous viennent exclusivement du ticket. Aucun client utilisateur,
|
|
autostart, catalogue Limusic, service, socket graphique utilisateur ou journal
|
|
RECOVERY_REQUIRED utilisateur n'a été modifié. Aucun commit/push.
|
|
|
|
## Faits, hypothèse et chaîne d'exécution
|
|
|
|
Le checkpoint fourni contient Firefox/Kitty horizontalement en 1, Limusic seul
|
|
en 10 et `focus=None`. Au login, `focus_origin={con_id:null,workspace:"1"}` est
|
|
une preuve de workspace explicite, différente d'un focus indisponible
|
|
`{con_id:null,workspace:null}`. Les trois clients sont observés, trois ancres
|
|
créées/nettoyées, aucune permutation effectuée. Le cleanup a pu écraser le
|
|
premier écart : la transition Limusic5 → Firefox13 associée à `kill` de l'ancre8
|
|
ne prouve donc pas le déclencheur initial.
|
|
|
|
Ordre effectif des sources de cette copie :
|
|
|
|
1. `Executor.apply` vérifie deux arbres et la session, prend le lock local,
|
|
puis `_apply` fait preflight, crée Recovery et la baseline. Le lock ne
|
|
verrouille ni l'input utilisateur, ni les autres clients IPC.
|
|
2. `Anchors.__init__` lance le helper GTK, sans encore créer de fenêtre.
|
|
`Guard` découvre/suspend autotiling avant de répondre ready. Cela ne constitue
|
|
pas une barrière générale du compositeur ni des autres processus.
|
|
3. `stage_node` admet les clients existants puis émet leur déplacement via
|
|
`RecordedSway` → `Recovery.command`. Pour le scénario fourni, **la première
|
|
mutation d'un client est le déplacement de Limusic existant de 10 au stage**.
|
|
Le cas d'un client déjà actif peut changer le focus à cet instant ; aucune
|
|
nouvelle application n'est encore lancée. Déplacer le dernier client peut
|
|
aussi faire disparaître le workspace 10 et compliquer le recovery ultérieur.
|
|
4. `Anchors.build` sélectionne les workspaces et outputs par IPC journalisé,
|
|
puis écrit `create` sur le stdin du helper. **La première création d'ancre
|
|
est la première admission de nouvelle surface susceptible de prendre le
|
|
focus sans passer par `RecordedSway.command`**. Les `focus`/`layout` suivants
|
|
sont journalisés. L'exception existante `anchor_owned` utilise PID/pidfd et
|
|
identités exactes du helper ; ce n'est pas une preuve de cause de l'input.
|
|
5. L'executor sélectionne le stage puis appelle `launch`. Les Popen sont
|
|
séquentiels, leurs mappings sont asynchrones : ordre de lancement différent
|
|
d'ordre de mapping. Limusic reçoit toujours zéro lancement. Les autres
|
|
clients peuvent prendre le focus pendant l'attente des cardinalités.
|
|
6. Les groupes sont constitués par app_id. Le provider Firefox résout les UUID
|
|
**avant** l'admission Recovery des nouveaux clients. `capture/resolve` lit
|
|
l'arbre et publie une requête runtime ; le companion change `titlePreface`,
|
|
sans demander `focused:true`. Le native host ne commande pas Sway. Une UUID
|
|
exacte ne prouve pas qui a changé le focus pendant l'attente du provider.
|
|
7. La boucle `captured` admet les identités puis stage les clients non présents
|
|
dans le stage. La première commande suivante peut constater le focus modifié.
|
|
Avant le patch, même en conflit, une commande prédite sans changement de focus
|
|
pouvait partir ; le refus sur une permutation ou un workspace était tardif.
|
|
Le guard nettoie ses ancres et relit Recovery, puis l'executor relit à son
|
|
tour Recovery. Plusieurs observateurs pouvaient remplacer le même diagnostic.
|
|
|
|
**Hypothèse plausible, non prouvée pour le login :** un map de nouveau client ou
|
|
une transition liée aux ancres explique le premier écart, particulièrement avec
|
|
un workspace initial sans feuille. Le nombre de fenêtres ou l'absence de swap
|
|
ne suffit pas à discriminer un map, un input, un helper ou une commande IPC.
|
|
|
|
## Garanties et limites externes
|
|
|
|
Le [manuel IPC Sway](https://man.archlinux.org/man/sway-ipc.7.en) décrit les
|
|
réponses de commande, GET_TREE et les événements de fenêtre. Les informations
|
|
`focused`, `focus`, `id`, `pid` décrivent l'état, pas une autorité causale de
|
|
transaction. Ni GET_TREE suivi de RUN_COMMAND ni une souscription aux événements
|
|
ne fournissent un compare-and-swap atomique du focus avec preuve de sa cause.
|
|
Un `tick` peut aider à ordonner les observations ; il n'exclut pas l'input.
|
|
|
|
Dans [Sway 1.11, view.c, should_focus/view_map](https://github.com/swaywm/sway/blob/1.11/sway/tree/view.c),
|
|
une surface mappée dans l'espace actif peut recevoir le focus. Le premier client
|
|
d'un espace vide peut même contourner `no_focus`. C'est un mécanisme plausible,
|
|
pas la preuve du comportement exact du SwayFX installé (0.6, basé sur 1.12.0).
|
|
Une règle globale `no_focus [app_id=...]` ne constitue donc pas la correction.
|
|
|
|
Le [protocole layer-shell](https://wayland.app/protocols/wlr-layer-shell-unstable-v1#zwlr_layer_surface_v1:enum:keyboard_interactivity)
|
|
distingue absence d'intérêt clavier, exclusivité et activation à la demande ;
|
|
l'interactivité est appliquée au commit de surface. Deux observations QML de
|
|
retrait ne constituent pas une barrière Sway ni la garantie qu'aucune autre
|
|
layer ne réapparaisse. Le focus workspace explicitement validé ne doit pas être
|
|
remplacé par une feuille tirée de la pile inactive.
|
|
|
|
[Autotiling amont](https://github.com/nwg-piotr/autotiling/blob/master/autotiling/main.py)
|
|
réagit aux événements IPC et émet des commandes de split. La suspension vérifiée
|
|
des helpers connus est utile ; elle n'annule pas une commande déjà soumise et
|
|
n'empêche pas le démarrage d'une nouvelle instance après découverte. Aucun de
|
|
ces mécanismes n'établit l'origine d'un changement de focus du lot restauré.
|
|
|
|
## Patch et régressions
|
|
|
|
`Recovery.record_focus_conflict` publie dès le premier constat et conserve cette
|
|
observation avec `setdefault` ; les chemins avant/après commande, refus de
|
|
préservation et entrée recovery l'utilisent. La phase indique où l'écart a été
|
|
**observé**, pas son événement causal. Les anciens journaux restent lisibles,
|
|
sans migration ni réécriture de leur observation historique ; ce qui a été
|
|
écrasé ne peut pas être retrouvé rétroactivement.
|
|
|
|
Après `focus_conflict`, `check_focus_preservation` refuse toute nouvelle commande
|
|
mutante du journal, même prédite neutre. Cela peut laisser davantage de clients
|
|
au stage qu'auparavant : le recovery partiel n'est plus un moyen de continuer
|
|
après une ambiguïté. Le guard conserve la responsabilité d'arrêter son propre
|
|
helper et de reprendre ses helpers suspendus ; il ne tue jamais les clients.
|
|
|
|
`admit` vérifie désormais session, con_id, PID, app_id et starttime aussi lors
|
|
d'une réadmission. L'origine épinglée ne change jamais. Cette vérification ne
|
|
résout **pas** la première admission par app_id ni l'intervalle entre lecture et
|
|
commande IPC ; aucune permission de focus ou de déplacement n'est ajoutée.
|
|
|
|
Les régressions inertes couvrent workspace sans feuille, mapping tardif de
|
|
Firefox/Kitty après stage de Limusic existant, refus sans nouvel IPC, préservation
|
|
du premier écart malgré cleanup/relecture, changement de session/PID/starttime/
|
|
app_id, faux app_id d'ancre et mutation neutre après focus tiers. Les tests
|
|
existants couvrent également le crash mid-IPC avec intention pending. La
|
|
simulation des postconditions utilise le prédicteur existant : ce n'est pas
|
|
une vérification indépendante des règles du compositeur.
|
|
|
|
## Validation et reproduction proposée sur l'hôte
|
|
|
|
Résultats locaux : **23 tests focus/recovery, 27 tests backend, 12 scénarios QML
|
|
PySide6 et 5 contrôles QtTest passent**. La validation du schéma de la nouvelle
|
|
fixture et `git diff --check` passent également. Le runner QML doit être celui
|
|
de Qt6 (`/usr/lib/qt6/bin/qmltestrunner`) ; le lanceur générique a échoué sans
|
|
sortie. Trois nouvelles régressions ont aussi été exécutées contre Recovery de
|
|
HEAD dans une copie de test : elles échouent avant patch (dont émission de la
|
|
commande interdite après focus tiers et réadmissions non vérifiées), puis passent
|
|
avec le patch.
|
|
|
|
Les preuves locales sont sous `build/real-focus-env/` (logs et `results.json`,
|
|
ainsi que `baseline-regressions.log`).
|
|
Les HOME/XDG/runtime de validation sont privés dans cette copie ; les variables
|
|
de sockets et de bus utilisateur sont retirées. Le SwayFX installé échoue avant
|
|
le démarrage du serveur : `drmGetDevices2 failed`, `Cannot create GLES2 renderer:
|
|
no DRM FD available`. Aucun résultat fonctionnel headless n'est revendiqué.
|
|
|
|
Nouvelle fixture composée `tests/session_v2/login_focus.py` : vrais
|
|
Executor/Recovery/Guard/Anchors, Limusic synthétique déjà présent en 10, Firefox
|
|
et Kitty synthétiques absents, workspace 1 sans feuille et checkpoint
|
|
`focus=None`. Elle remplace les lancements applicatifs par des surfaces GTK
|
|
possédées, et le provider par une UUID synthétique stable. Le contrôleur
|
|
focalise explicitement le nouveau client juste après map et avant retour du
|
|
lancement. L'assertion attend un refus avant le premier swap, conserve les trois
|
|
clients et vérifie l'unicité de Limusic. La découverte des helpers utilisateur
|
|
est neutralisée ; aucune validation d'autotiling réel n'en découle.
|
|
|
|
À exécuter côté hôte **dans cette copie**, avec un environnement temporaire
|
|
fourni par ce harnais (les tests ne doivent jamais hériter d'un socket réel) :
|
|
|
|
```sh
|
|
python3 -B tests/session_v2/validate_login_focus.py
|
|
```
|
|
|
|
Le harnais lance les tests Python, QML et la fixture composée. Il retourne un
|
|
code non nul si un contrôle échoue, y compris au démarrage headless ; un refus
|
|
du renderer est une validation indisponible, pas un PASS fonctionnel. Pour exécuter la
|
|
fixture seule, reprendre les variables privées et les suppressions de variables
|
|
sockets/bus définies dans ce script. La fixture crée ensuite elle-même un nouveau
|
|
Lab et utilise exclusivement son socket enfant explicite. Un autre compositeur
|
|
headless compatible peut être sélectionné par `LABFY_TEST_SWAY` absolu et
|
|
`LABFY_TEST_RENDERER=pixman`, sans remplacer le compositeur installé.
|
|
|
|
Le résultat attendu est **un refus sûr**, pas un restore réussi. Compléter ensuite
|
|
sur hôte par les variantes suivantes : focus naturel sans injection, ancre au
|
|
map, input tiers avant/après map, nouveau client concurrent de même app_id,
|
|
réapparition d'une layer, workspace10 disparu, multi-output, mort du helper,
|
|
crash avant/après IPC et réutilisation PID/starttime/con_id. Chaque variante doit
|
|
avoir un runtime neuf et garder son arbre avant/après et son premier diagnostic.
|
|
Même réussie, une telle expérience ne valide pas le vrai login.
|
|
|
|
## Stratégie testable restant bloquée
|
|
|
|
Ne pas ajouter d'exception « focus sur app_id autorisé » ou « con_id dans le lot ».
|
|
Épingler session/con_id/PID/starttime est nécessaire mais ne suffit pas à décider
|
|
si l'utilisateur a focalisé ce même client. Le PID du Popen Firefox peut aussi
|
|
être un lanceur court relayé vers un processus existant.
|
|
|
|
Une solution candidate devrait séparer découverte/mapping et mutations de
|
|
placement, avant de déplacer Limusic, puis exiger une frontière d'autorité
|
|
explicite et vérifiable : identité de chaque surface, preuve de lancement quand
|
|
applicable, et contrôle des transitions de focus sur l'autorité du compositeur.
|
|
Une trace d'événements bornée et les identités épinglées permettraient de tester
|
|
les hypothèses ; elles ne suffisent pas seules à autoriser une divergence.
|
|
Une API compositeur conditionnelle avec génération et provenance serait une
|
|
piste plus forte, hors du petit patch actuel. Une autre politique nécessiterait
|
|
un acquittement utilisateur explicite après mapping, avec un protocole séparé
|
|
et testé ; ce n'est pas implicitement autorisé par le clic initial Restaurer.
|
|
|
|
**Décision en attente :** choisir une stratégie capable de prouver cette
|
|
frontière avant toute exception. En attendant, garder les refus et les journaux.
|
|
Ni les données du ticket ni les API actuellement exploitées ne permettent de
|
|
revendiquer une réparation automatique sûre du scénario réel.
|
|
|
|
## Procédure DRY-RUN proposée pour le client déjà bloqué — non exécutée
|
|
|
|
1. Ne pas relancer restore, ne pas choisir une action qui efface le diagnostic,
|
|
ne pas tuer/déplacer Limusic, ne pas le relancer. Préparer une inspection avec
|
|
l'utilisateur ; aucun de ces accès à sa session n'est autorisé par ce ticket.
|
|
2. Après autorisation distincte d'inspection, conserver des copies privées des
|
|
journal/reçu/rapport et leur empreinte avant toute action. Vérifier leurs
|
|
transaction_id, session et stage concordants ; ne pas déduire l'appartenance
|
|
au lot du préfixe du workspace. Relever intention, pending, recovered,
|
|
focus_origin/expected/conflict et la première observation disponible.
|
|
3. Établir explicitement le socket et l'identité du compositeur de cette session
|
|
(UID, chemin, device/inode/ctime utilisés pour le hash `Sway.session`). Relever
|
|
get_tree/get_outputs/get_workspaces avec ce socket explicitement choisi.
|
|
Comparer à la session du journal ; si elle diffère, STOP, aucune commande.
|
|
4. Pour **chaque** con_id candidat, comparer PID, `/proc/PID/stat` starttime,
|
|
app_id et session avec origin/expected. Vérifier le PID vivant et son UID,
|
|
ainsi que l'absence de réutilisation/ambiguïté. App_id ou titre seul ne vaut
|
|
jamais identité. Une intention pending interdit une application mécanique
|
|
de l'instruction suggérée dans le reçu.
|
|
5. Construire un tableau sans exécution : identité complète, état observé,
|
|
origin, expected, workspace/output actuels, workspace/output désirés,
|
|
flottement/géométrie, statut pending et écarts. Vérifier le contenu du stage,
|
|
les clients tiers et l'existence des outputs actifs. Vérifier où se trouve
|
|
l'espace10 : absent, déjà utilisé, ou déplacé d'output. Ne pas supposer que 10
|
|
est libre ni que tout client du stage appartient à la transaction.
|
|
6. Proposer uniquement les mouvements nominativement justifiés par ce tableau,
|
|
avec critères con_id explicitement issus de l'arbre et conditions de refus.
|
|
Faire approuver **explicitement** par l'utilisateur ce plan précis, y compris
|
|
une éventuelle création d'espace10, son output et un changement de focus.
|
|
Sans cette autorisation, DRY-RUN terminé, zéro mutation.
|
|
7. Après une éventuelle autorisation future, refaire immédiatement les preuves
|
|
session/PID/starttime/emplacement/outputs/focus avant chaque action. Tout écart
|
|
ou concurrence annule le plan. Ne jamais faire de boucle app_id, kill client,
|
|
lancement de Limusic, focus de feuille devinée ou rollback forcé.
|
|
8. Vérifier ensuite toutes les identités et emplacements demandés, le contenu du
|
|
stage, les outputs, les clients tiers et le focus final convenu. Conserver
|
|
avant/après et résultats nominatifs. Un stage encore occupé ou une preuve
|
|
manquante reste RECOVERY_REQUIRED.
|
|
9. **Archiver seulement après ces preuves et accord explicite**, avec copies,
|
|
empreintes, session/transaction et reçu de récupération manuelle. Ne pas
|
|
transformer un échec en succès automatique, ni simplement supprimer le
|
|
journal pour débloquer le bouton. Aucune de ces étapes n'a été exécutée ici.
|