437 lines
27 KiB
Markdown
437 lines
27 KiB
Markdown
# Companion Firefox Session Restore V2
|
||
|
||
Sources de la version **1.1.2**, même ID `session-v2@labfy.org`, même clé
|
||
`labfy.session.window_uuid`, mêmes permissions `sessions`, `nativeMessaging`.
|
||
Le build local produit un XPI non signé. Son installation permanente nécessite
|
||
une signature Mozilla, en conservant cet ID et les identités de fenêtres existantes.
|
||
Aucun accès aux profils, au runtime utilisateur ou au socket Sway réel n'est
|
||
nécessaire pour les validations décrites ici. Elles ne réalisent aucun déploiement permanent.
|
||
|
||
L'expérience visée est **installation seule, puis Firefox normal, Ctrl+N compris**.
|
||
Le bouton et le panneau provisoires de 1.1.0 sont supprimés. Le background ne
|
||
crée aucune fenêtre. Une fenêtre ambiguë peut rester sans identité et faire
|
||
échouer la capture ; aucune action quotidienne de remplacement n'est imposée.
|
||
|
||
## Correctif 1.1.2 : séparer les webapps du lot UUID
|
||
|
||
Une fenêtre Firefox Taskbar Tab peut être exposée par l'API WebExtension comme
|
||
`type: normal`, alors que SessionStore l'exclut du stockage de session. Un UUID
|
||
attribué dans sa fenêtre pendant une session peut donc manquer au login suivant.
|
||
La 1.1.1 validait l'ensemble des fenêtres normales avant toute publication : une
|
||
webapp sans UUID bloquait aussi la capture du navigateur ordinaire. Si toutes
|
||
les valeurs étaient présentes, l'ordre des fenêtres pouvait masquer ce défaut :
|
||
le host ne corrélait que `firefox`/`org.mozilla.firefox`, et un navigateur publié
|
||
avant la webapp pouvait suffire au backend avant l'échec ultérieur du lot.
|
||
|
||
Sources Firefox vérifiées, y compris dans les `omni.ja` installés :
|
||
`TaskbarTabsWindowManager.sys.mjs` ouvre avec toolbar et sans dialog ;
|
||
[WindowBase.type](https://searchfox.org/firefox-main/source/toolkit/components/extensions/parent/ext-tabs-base.js)
|
||
classe alors la fenêtre comme normale ;
|
||
[windows.getAll](https://searchfox.org/firefox-main/source/browser/components/extensions/parent/ext-windows.js)
|
||
filtre ce type seulement. Dans SessionStore, `getCurrentState()` et la collecte
|
||
des fenêtres fermées excluent explicitement `isTaskbarTab`. L'API Window ne
|
||
fournit aucun attribut public `isTaskbarTab` ou `app_id` permettant ce filtrage.
|
||
|
||
La 1.1.2 ajoute une classification native avant toute lecture ou attribution
|
||
UUID. Elle conserve les permissions `sessions` et `nativeMessaging`, l'ID de
|
||
l'extension et la clé de session existants. La demande est exactement :
|
||
|
||
```json
|
||
{"op":"classify-window","runtime_token":"0123456789abcdef0123456789abcdef"}
|
||
```
|
||
|
||
Le host corrèle le préfixe éphémère, puis vérifie les app_ids exacts des entrées
|
||
Firefox et WhatsApp gérée du catalogue. Sa réponse fermée comporte `result`,
|
||
`kind`, `runtime_token` réémis et `con_id` positif. `kind` vaut exclusivement
|
||
`browser` ou `managed-webapp`. Un app_id inconnu, une autre webapp, une collision,
|
||
un timeout ou une réponse invalide refuse l'opération ; aucune erreur n'est
|
||
interprétée comme une permission d'ignorer une fenêtre. La classification ne
|
||
publie aucun enregistrement UUID. `map-window` reste réservé à Firefox ordinaire.
|
||
|
||
Le background exclut seulement une webapp positivement attestée avant
|
||
`getWindowValue`, validation, observation ou mint. Une fenêtre normale sans UUID
|
||
continue à refuser la capture exacte. Les règles de protection des anciennes
|
||
fenêtres et d'attribution aux nouveaux Ctrl+N restent identiques.
|
||
|
||
Les preuves `{kind, con_id}` sont bornées à 256 fenêtres, locales à l'instance et
|
||
supprimées sur fermeture. La première observation classe une fenêtre une fois ;
|
||
une erreur n'est pas retentée par la boucle idle. Chaque capture reclassifie
|
||
fraîchement toutes les fenêtres, y compris celles qui seront exclues, puis
|
||
revalide après l'attente d'extData. Chaque mint revalide aussi la classe avant
|
||
écriture. Un changement de classe ou de con_id interdit l'opération jusqu'à la
|
||
fermeture de cette fenêtre. L'ensemble brut des fenêtres doit rester stable
|
||
pendant l'opération. Aucun marquage ou `map-window` périodique permanent n'est
|
||
ajouté en veille.
|
||
|
||
La corrélation ajoute des préfixes brefs lors du premier classement, d'une capture
|
||
ou d'un mint ; elle utilise les mêmes garanties de nettoyage et limites API que
|
||
le protocole existant. Elle ne rend pas le sessionstore transactionnel. Un host
|
||
ancien ne comprenant pas la classification provoque un refus conservateur ; le
|
||
nouveau host reste compatible avec les messages de l'extension 1.1.1.
|
||
|
||
Les tests VM de cette révision couvrent la webapp au premier ID sans UUID, son
|
||
exclusion des lectures et écritures, la normale sans UUID toujours refusée, les
|
||
réponses non exactes, le changement de classe/con_id, l'invalidation sur close,
|
||
le changement d'ensemble et l'absence de marquage répété en idle. Le code testé
|
||
est celui du XPI. La validation Python du host reste séparée de tout profil ou
|
||
socket utilisateur. La régression a également été reproduite puis corrigée sous
|
||
Firefox réel 158.0.1 dans un profil vierge et un Sway privé (détails plus bas).
|
||
|
||
La mise à jour nécessite un nouveau XPI signé Mozilla, avec le même ID. Déployer
|
||
le host compatible avant l'installation de l'extension signée ; son redémarrage
|
||
déconnectera l'ancien port natif et ouvrira le nouveau host. Ne pas modifier le
|
||
contenu d'un XPI signé, désactiver les signatures ou réattribuer les anciens UUID.
|
||
Le build ci-dessous prépare seulement le XPI déterministe non signé : aucune
|
||
signature, publication AMO ou installation n'est réalisée par le build.
|
||
|
||
## Preuves disponibles et limite fondamentale
|
||
|
||
La 1.0.0 pouvait attribuer après `undefined` et seulement 200 ms, donc écraser
|
||
une identité arrivée plus tard. L'incident historique (onglets conservés, UUID
|
||
modifié entre deux logins) est compatible avec cette course, sans en prouver la
|
||
cause précise. L'extension ne répare ni cet UUID perdu ni les checkpoints.
|
||
|
||
Sources primaires consultées pour cette révision :
|
||
|
||
- [getWindowValue](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/sessions/getWindowValue)
|
||
conserve les valeurs lors de close/restore, avec un autre windowId runtime.
|
||
Son exemple inclut les fenêtres restaurées dans `onCreated`. `undefined` et
|
||
rejet sont deux résultats distincts ; aucun délai maximal d'extData n'est fourni.
|
||
- [onChanged](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/sessions/onChanged)
|
||
signale des changements de liste fermée, sans provenance de fenêtre.
|
||
- [Window.sessionId](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/windows/Window)
|
||
identifie une fenêtre obtenue via l'API sessions. Sa présence est utilisée
|
||
uniquement pour protéger ; son absence ne prouve jamais une création neuve.
|
||
- [sessions.restore](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/sessions/restore)
|
||
renvoie la session restaurée lorsque l'extension appelle elle-même cette API.
|
||
Cela n'identifie pas directement une restauration effectuée par l'utilisateur.
|
||
`getRecentlyClosed` fournit une liste bornée, pas une correspondance certaine
|
||
entre ses éléments et chaque événement `windows.onCreated`. Nous ne l'utilisons
|
||
pas comme preuve, et n'inspectons aucun contenu d'onglet.
|
||
- [Événements SessionStore Gecko](https://firefox-source-docs.mozilla.org/browser/components/sessionstore/docs/events.html)
|
||
documente `sessionstore-windows-restored` comme notification interne de fin du
|
||
startup. Ce n'est pas un événement de WebExtension standard. Les
|
||
[API privilégiées/Experiments](https://firefox-source-docs.mozilla.org/toolkit/components/extensions/webextensions/basics.html)
|
||
ne constituent pas une voie AMO ordinaire pour ce candidat Firefox Release.
|
||
- [runtime.onInstalled](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/runtime/onInstalled)
|
||
distingue explicitement `install`, `update`, `browser_update`.
|
||
|
||
**Conclusion de conception :** les observations répétées et une période calme
|
||
réduisent le risque ; elles ne prouvent pas la fin de toutes les restaurations.
|
||
Une restauration manuelle/tardive sans signal exploitable, dont l'ancienne valeur
|
||
reste absente jusqu'après la dernière lecture, est indiscernable d'un Ctrl+N.
|
||
Elle peut recevoir un nouvel UUID dans ce compromis automatique. Une valeur
|
||
ancienne arrivant entre la dernière lecture et l'écriture peut encore être
|
||
écrasée : `setWindowValue` n'offre pas de compare-and-set. Les contrôles après
|
||
écriture ne suppriment pas ce risque. **Aucune garantie absolue de classification
|
||
ou d'absence d'écrasement de données jamais encore observées n'est revendiquée.**
|
||
Garantir cela pour toutes les restaurations exigerait une preuve supplémentaire
|
||
fournie par Firefox ; refuser tout mint sur `onCreated` supprimerait l'auto Ctrl+N.
|
||
L'automatisme de 1.1.1 est conservé avec refus des ambiguïtés observables.
|
||
|
||
## Initialisation : BOOT, READY, PROTECTED
|
||
|
||
Les listeners sont enregistrés avant tout appel asynchrone. Le premier `getAll`,
|
||
sans populate, capture les IDs de la baseline. Une fenêtre émise **avant la
|
||
résolution** de cette photographie reste ambiguë, même si `getAll` la contient.
|
||
Un événement reçu ensuite, y compris pendant BOOT, devient candidat potentiel :
|
||
`type` normal ou absent, sans `sessionId` positif. Sa présence dans
|
||
`getAll({windowTypes:["normal"]})` et une classification native fraîche `browser`
|
||
seront exigées avant toute écriture. Les types explicitement différents de normal
|
||
sont ignorés ; une webapp de type normal est exclue seulement après attestation.
|
||
|
||
**Auto-baseline sans clic, sous attestation native.** L'événement
|
||
`onInstalled(reason=install)` est mémorisé qu'il précède ou suive le premier
|
||
`getAll`. Il autorise seulement une tentative de lot après stabilisation.
|
||
`update`, `browser_update` et restart ne l'autorisent jamais. Avant **chaque**
|
||
écriture de baseline, l'extension envoie exactement :
|
||
|
||
```json
|
||
{"op":"bootstrap-eligibility"}
|
||
```
|
||
|
||
Le host répond avec exactement deux champs, sans chemin ni contenu utilisateur :
|
||
|
||
```json
|
||
{"result":"OK","baseline_enrollment_allowed":true}
|
||
```
|
||
|
||
`true` signifie que le checkpoint automatique V2 canonique
|
||
`automatic/last.json` est absent **et** que `sessions` est absent ou vide
|
||
(aucune sauvegarde nommée ni entrée inattendue), à l'instant du contrôle, sous l'autorité
|
||
`XDG_STATE_HOME` ou son fallback HOME habituel. Le chemin est commun au store et
|
||
au host ; l'extension ne le reçoit pas. Le parcours utilise des descripteurs de
|
||
répertoires et `NOFOLLOW`, sans créer le store, ouvrir le checkpoint ou suivre un
|
||
lien. Toute présence, même fichier corrompu, illisible, lien ou FIFO, refuse.
|
||
Le host examine les deux branches même si l'une est absente. `sessions` est
|
||
énuméré par `os.listdir(dirfd)` sans ouvrir ses entrées ; tout fichier, lien,
|
||
FIFO ou sous-répertoire refuse. Le chemin est limité à 4096 octets ; un
|
||
répertoire `sessions` dont la taille de métadonnées dépasse 64 Kio refuse
|
||
avant énumération. Ce budget conservateur ne certifie aucune absence.
|
||
Une erreur, un parent inaccessible, un namespace non privé ou un chemin invalide
|
||
refuse aussi. Le host revalide le socket et l'UID du pair Sway par `GET_VERSION`,
|
||
en lecture seule, sans demander l'arbre des fenêtres pour cette opération.
|
||
|
||
Seules les fenêtres de la photographie initiale encore sans valeur, sans
|
||
identité déjà observée ni tentative d'écriture et sans signal protecteur sont
|
||
éligibles. Deux fenêtres initiales sans UUID reçoivent ainsi automatiquement
|
||
deux UUID distincts si le host atteste l'absence à chaque contrôle et si les
|
||
relectures Firefox restent stables. Les fenêtres signalées avant la photographie
|
||
ou avec `sessionId` positif restent protégées. Après réponse positive : relecture
|
||
intégrale du lot, révision inchangée et dernière absence exigées avant écriture.
|
||
|
||
**Une installation seule ne garantit donc pas l'enrôlement.** Host indisponible,
|
||
réponse non exacte, refus, déconnexion ou timeout de 5 secondes : aucune
|
||
attribution de baseline concernée ; elle reste en quarantaine. Une activité
|
||
pendant le lot révoque la tentative. Aucun retry d'autorisation baseline dans
|
||
cette instance, même après reconnexion ou nouvel événement install. Si le
|
||
checkpoint apparaît entre deux écritures, la seconde est refusée ; aucune
|
||
annulation de l'UUID déjà écrit. Les futurs Ctrl+N, y compris ceux reçus pendant
|
||
BOOT après photographie, conservent leur automatisme indépendamment du refus.
|
||
|
||
Cette attestation concerne les checkpoints V2 automatiques et nommés à leurs
|
||
emplacements canoniques. Elle ne prouve ni l'absence historique de sauvegardes,
|
||
ni l'absence de sauvegardes déplacées, ni la provenance des restaurations tardives
|
||
Firefox. Elle n'est ni un verrou
|
||
persistant, ni un CAS ACID, ni un observer sessionstore ; un checkpoint peut
|
||
apparaître après la dernière réponse. Aucun checkpoint ou navigateur n'est
|
||
modifié par cette observation native.
|
||
|
||
BOOT attend au moins 30 secondes depuis le chargement, 2 secondes sans activité
|
||
et l'expiration du guard de 5 secondes suivant `sessions.onChanged`. Ensuite la
|
||
phase passe READY même si des anciennes fenêtres restent ambiguës. Celles-ci
|
||
bloquent **la capture exacte**, mais ne condamnent plus les futurs Ctrl+N, y
|
||
compris après 60 secondes et après fermeture de l'ancienne fenêtre. PROTECTED
|
||
reste un refus opérationnel, notamment si la photographie initiale échoue ; un
|
||
timeout API exige un redémarrage. Ces délais sont des budgets, pas une barrière
|
||
sessionstore ni une preuve de nouveauté.
|
||
|
||
Un Ctrl+N à 10 secondes, après photographie initiale, reste en attente puis reçoit
|
||
automatiquement son UUID après stabilisation et observation. Une restauration
|
||
sans signal dans ce même intervalle est indiscernable : le timing ne la rend pas
|
||
« prouvée nouvelle ». Une identité reçue pendant l'attente est préservée.
|
||
|
||
## Fonctionnement automatique après READY
|
||
|
||
Un passage périodique unique par seconde observe le lot complet. Chaque absence
|
||
candidate doit persister durant 2 secondes de lectures espacées de 250 ms. Dans
|
||
le cas normal, l'UUID apparaît environ 2 à 3 secondes après Ctrl+N, sans action sur
|
||
l'extension. Les anciennes fenêtres ambiguës restent exclues, sauf la baseline initiale
|
||
explicitement admissible sous l'attestation décrite ci-dessus.
|
||
|
||
Avant chaque écriture : relecture intégrale, ensemble inchangé, identités connues
|
||
inchangées, UUID v4 valide, absence de collisions visibles, révision inchangée.
|
||
Deux Ctrl+N simultanés ou espacés d'une seconde reçoivent des identités distinctes
|
||
après réobservation du lot stable. L'ordre numérique des IDs sert seulement à
|
||
sérialiser les opérations. Aucune unicité sur toutes les fenêtres fermées de
|
||
l'histoire n'est prouvée ; la génération repose sur `crypto.randomUUID`.
|
||
|
||
`sessions.onChanged` peut signaler une simple fermeture d'onglet. Il invalide le
|
||
passage en lecture et diffère les candidats pendant le guard, sans les mettre
|
||
en quarantaine définitive. Une modification de révision ou d'ensemble avant
|
||
écriture abandonne le passage ; les mêmes candidats sont réobservés après calme.
|
||
Les rejets de lecture, identités invalides ou divergentes et écritures incertaines
|
||
restent des refus conservateurs. La présence de `sessionId` ou un événement avant
|
||
la photographie initiale protège individuellement la fenêtre concernée.
|
||
|
||
Une valeur observée, même dans un lot partiellement rejeté ou invalide, ne peut
|
||
jamais devenir une autorisation de mint. Changement,
|
||
disparition ou collision refuse le lot. Aucune suppression de valeur et aucun
|
||
rollback d'identité. Une seule tentative d'écriture par fenêtre/instance ; un
|
||
rejet peut cacher un effet appliqué et n'est jamais réessayé. Vérification par
|
||
relecture avant de libérer l'opération. Un restart perd toute autorité de mint en
|
||
attente et ne conserve comme vérité que les valeurs de session.
|
||
|
||
Ni onglets, ni URL, ni titres, ni géométrie, ni ordre, ni PID n'identifient une
|
||
fenêtre. Aucune lecture/écriture de tab data ; déplacer des onglets n'entraîne
|
||
aucun transfert d'identité ni fusion de fenêtres.
|
||
|
||
## Capture native et bornes
|
||
|
||
La capture native reste **non-mutante pour l'identité et les onglets**. Elle relit
|
||
un lot incomplet pendant 2 secondes puis refuse sans publication. Elle peut
|
||
récupérer une valeur arrivée plus tard lors d'une nouvelle capture. Lot complet
|
||
revalidé avant publication et UUID relu avant chaque message. Aucun protocole
|
||
atomique avec Firefox ou transaction native de lot n'est prétendu.
|
||
Le préfixe `titlePreface` éphémère de corrélation, déjà présent dans 1.1.0, reste
|
||
la seule mutation de présentation ; nettoyage en `finally` et au démarrage.
|
||
Si l'appel de marquage expire puis se règle tardivement, un seul effacement de
|
||
secours est lancé après son règlement, sans relancer le marquage. Si l'appel ne
|
||
se règle jamais, ou si l'effacement bloque/rejette, le préfixe peut rester visible
|
||
jusqu'au redémarrage/nettoyage réussi. Aucune API WebExtension ne permet d'assurer
|
||
une borne de disparition face à une API définitivement bloquée ; le préfixe ne
|
||
peut donc pas être qualifié d'inconditionnellement éphémère.
|
||
Les titres eux-mêmes ne sont jamais lus.
|
||
|
||
Une seule opération active (initialisation/nettoyage, auto-mint ou capture) ;
|
||
captures concurrentes coalescées sans queue, sans accumuler des opérations.
|
||
Une demande native en vol ; timeout 5 secondes ; reconnexion au plus une fois par
|
||
seconde, sans map-window en idle. Les classifications de première observation
|
||
sont mises en cache, sans répétition périodique. Le backend existant garde son timeout de 6 secondes :
|
||
un checkpoint trop tôt ou durant un mint peut échouer, il ne reçoit pas de faux
|
||
succès ni d'identité inventée par la capture.
|
||
|
||
Chaque appel Firefox a une borne de 2 secondes. Un timeout arrête les appels
|
||
Firefox ordinaires de cette instance jusqu'au redémarrage (seul l'effacement
|
||
de secours après règlement du marquage peut encore être tenté) ; les promesses ne sont pas
|
||
annulables et un effet tardif reste possible. Les lectures sœurs sont toutes
|
||
attendues même si l'une rejette. Limite locale conservée : 256 fenêtres.
|
||
Les ensembles en mémoire sont bornés aux fenêtres suivies et nettoyés sur close.
|
||
Le provider Python exact, le format et le contenu des checkpoints et les
|
||
applications SwayFX restent inchangés. Le host ajoute les opérations
|
||
d'observation bootstrap et de classification au protocole de mapping existant. Une capture refusée ne publie pas un message d'erreur incompatible :
|
||
le provider existant échoue faute du lot demandé. Le diagnostic facultatif
|
||
`runtime.sendMessage({op: "identity-status"})`, réservé à cette extension, retourne
|
||
phase, résultat WAIT/READY/REFUSED, dernier motif et compteurs ; il n'attribue rien.
|
||
Le résultat READY exige la phase READY, aucune erreur, aucune quarantaine et
|
||
aucun candidat en attente. La phase READY seule peut coexister avec un résultat
|
||
REFUSED pour ambiguïté historique. `prefixCleanupPending` expose un effacement
|
||
de secours non confirmé. Aucun panneau ni clic quotidien n'est ajouté.
|
||
|
||
## Validation et build local
|
||
|
||
Depuis la racine de cette copie :
|
||
|
||
```sh
|
||
node --test tests/firefox_session_v2/*.test.js
|
||
node --check firefox-extension/session-v2/background.js
|
||
python3 -B -m unittest discover -s tests/session_v2 -p 'test_*.py'
|
||
python3 -B tools/build-session-v2.py --extension-only
|
||
sha256sum build/session-v2/labfy-session-v2-unsigned.xpi
|
||
```
|
||
|
||
Le XPI déterministe ne contient que `manifest.json` et `background.js`.
|
||
`extension-manifest.json` indique SHA-256, `signed: false`, `installed: false`.
|
||
Deux builds locaux sont comparés octet pour octet. Le build complet historique
|
||
sans `--extension-only` reste disponible ; aucun helper n'est installé ici.
|
||
|
||
Lint ESLint 9.39.1 épinglé, outil isolé dans la copie (pas le validateur AMO) :
|
||
|
||
```sh
|
||
mkdir -p build/session-v2
|
||
touch build/session-v2/npm-user.conf build/session-v2/npm-global.conf
|
||
npm --userconfig "$PWD/build/session-v2/npm-user.conf" \
|
||
--globalconfig "$PWD/build/session-v2/npm-global.conf" \
|
||
exec --cache ./build/session-v2-npm-cache --yes --package eslint@9.39.1 -- \
|
||
eslint --no-config-lookup --no-ignore \
|
||
--rule 'no-unused-vars:error' --rule 'no-undef:error' \
|
||
--global browser --global crypto --global performance \
|
||
--global setTimeout --global clearTimeout --global setInterval \
|
||
firefox-extension/session-v2/background.js
|
||
```
|
||
|
||
Les tests Node exécutent le vrai background dans une VM : APIs WebExtension,
|
||
ports natifs et temps synthétiques. Des getters interdits détectent toute lecture
|
||
d'URL, titre, onglets, dimensions ou API tabs, même si le background attrape
|
||
l'exception. Ils couvrent installation avant/après start, Ctrl+N, boot ambigu,
|
||
extData tardives, restauration signalée, absence de signal (limite exposée),
|
||
erreurs et timeout, collisions, close, déplacements d'onglets, restart/update,
|
||
concurrence et reconnexion. Les 46 tests Node présents dans la copie initiale sont conservés avec leur
|
||
comportement protecteur lorsque le host n'atteste pas l'absence. Le nouveau P0
|
||
échoue contre le background pré-protocole conservé en preuve, puis passe contre
|
||
le correctif. La validation historique 1.1.1 comptait **58 tests Node** et **63 tests Python**
|
||
(les 43 backend préexistants et 20 tests bootstrap). La révision 1.1.2 conserve ces
|
||
58 scénarios Node et ajoute 10 régressions de classification : **68/68 PASS**.
|
||
Les tests ciblés du host passent **30/30** : les 20 tests bootstrap existants et
|
||
10 tests de classification. Ils vérifient l'enveloppe fermée, le jeton exact, les
|
||
classes du catalogue, les collisions même avec une surface inconnue, la réponse
|
||
sans contenu, l'absence de publication UUID et le refus de mapper une webapp.
|
||
Ces tests Python passent par le framing stdio réel du host avec HOME/XDG privés
|
||
et un faux socket Sway
|
||
possédé, y compris aller-retour bootstrap/map/bootstrap, checkpoint présent,
|
||
valide, corrompu ou inaccessible, sauvegarde nommée seule sans `last.json`,
|
||
répertoires vides, entrées inattendues dans `sessions`, liens, FIFO, permissions,
|
||
erreur d'énumération, enveloppes invalides et allowlist.
|
||
Les scénarios VM ajoutés couvrent les deux ordres install/start, le refus,
|
||
l'expiration, les réponses non exactes, la capture concurrente, le rollback,
|
||
la relecture avant écriture, le checkpoint apparu entre écritures et les
|
||
reconnexions sans retry baseline. Le risque de restauration indiscernable est
|
||
exposé par un test ; il ne constitue pas une preuve de classification parfaite.
|
||
|
||
Les validations VM historiques 1.1.1 ne lançaient pas Firefox réel et ne
|
||
prouvaient pas le comportement physique de SessionStore. Le test réel 1.1.2
|
||
ci-dessous ajoute une preuve de capture ; il ne constitue pas un logout/login
|
||
de la session utilisateur. Le minimum de manifeste reste Firefox 157. Le
|
||
déploiement permanent de 1.1.2 avec le même ID nécessite une signature Mozilla
|
||
et une installation autorisée ; les tests et le build ne signent ni ne publient.
|
||
|
||
### Régression Firefox réelle 1.1.2
|
||
|
||
Le 27 octobre 2026, le scénario a été exécuté sous Firefox **158.0.1** avec un
|
||
profil vierge, HOME/XDG privés, serveur HTTP loopback et Sway headless privés.
|
||
Marionette et l'installation temporaire de l'extension sont confinés à ce
|
||
profil TEST_ONLY ; aucun profil, cookie, checkpoint ou fenêtre utilisateur
|
||
n'est utilisé. La webapp est créée avant la fenêtre normale, sans UUID ;
|
||
la normale reçoit une identité fixture stable avant l'installation temporaire.
|
||
|
||
Avec la 1.1.1, la capture reproduit
|
||
`FIREFOX_IDENTITY_PROVIDER_UNAVAILABLE`. Avec le XPI 1.1.2 et le host de cette
|
||
révision, elle réussit : correspondance exacte UUID/con_id de la normale,
|
||
UUID normal inchangé et aucun UUID attribué à la webapp. Cela confirme aussi
|
||
que le préfixe éphémère d'une vraie Taskbar Tab atteint Sway et permet sa
|
||
classification native. Résultat final : **PASS**.
|
||
|
||
Reproduction du candidat construit dans cette copie :
|
||
|
||
```sh
|
||
python3 -B tests/session_v2/firefox_webapp_runtime.py \
|
||
--extension build/session-v2/labfy-session-v2-unsigned.xpi --expect success
|
||
```
|
||
|
||
Le script émet un résultat JSON avec seulement les verdicts et la version,
|
||
dans son répertoire temporaire TEST_ONLY. Il ferme ses processus et ressources
|
||
privés. Cette validation ne prouve pas la persistance au prochain login réel,
|
||
la signature AMO ni le déploiement permanent de l'extension.
|
||
|
||
## Native host
|
||
|
||
Après déploiement Stow, rendre le template `native-host.json.in` en remplaçant
|
||
`@ABSOLUTE_NATIVE_HOST_LAUNCHER@` par le chemin absolu de
|
||
`$HOME/.config/quickshell/labfy-sway/session-v2-native-host.py`.
|
||
Conserver le nom du host, le type `stdio` et l’allowlist exacte de l’extension.
|
||
Le wrapper doit rester exécutable.
|
||
|
||
Sur l’installation Linux validée, le manifeste est
|
||
`${XDG_CONFIG_HOME:-$HOME/.config}/mozilla/native-messaging-hosts/org.labfy.session_v2_identity.json`.
|
||
Le chemin historique `$HOME/.mozilla` pointe vers cette arborescence. Sur un
|
||
autre packaging Firefox, vérifier le répertoire Native Messaging utilisé avant
|
||
installation. Créer le manifeste avec permissions 0600 ; ne pas écraser un
|
||
host actif lors d’une simple mise à jour de documentation.
|
||
|
||
Le host accepte `map-window` avec UUID et jeton runtime, `classify-window`
|
||
avec son seul jeton runtime, ainsi que `bootstrap-eligibility` sans autre champ.
|
||
Les trois schémas sont fermés. La classification atteste seulement le type local ;
|
||
elle ne crée, ne modifie ni ne publie d'UUID.
|
||
Les messages sont bornés à 4096 octets ; la sortie standard est réservée au
|
||
framing Native Messaging. Le host lit l’arbre Sway uniquement pour corréler le préfixe
|
||
éphémère, sans exécuter de commande Sway mutatrice. L’extension retire ce
|
||
préfixe après corrélation. Le provider exige des observations récentes et un
|
||
publisher vivant ; une identité absente ou ambiguë produit un échec explicite.
|
||
|
||
Les UUID persistants appartiennent aux fenêtres Firefox ; les observations et
|
||
identifiants de processus restent privés dans le runtime. Ne pas publier ces
|
||
fichiers. Le provider demeure le candidat validé pour le profil supporté ; le
|
||
multi-profil n’est pas pris en charge. Les fenêtres et onglets sont restaurés
|
||
par Firefox, pas par une lecture de sessionstore côté Labfy.
|
||
|
||
Voir [Session Restore V2](../../docs/session-restore-v2.md) pour le cycle complet,
|
||
les tests et les limites.
|
||
|
||
## Verdict historique du correctif 1.1.1
|
||
|
||
**SOURCE_TESTED — AUTO_BOOTSTRAP_CONDITIONNEL** : dans la VM, première
|
||
installation avec deux fenêtres sans UUID et absence attestée de checkpoint :
|
||
deux identités stables attribuées automatiquement. Réinstallation avec checkpoint
|
||
présent, contrôle inaccessible ou erreur : refus conservateur. Les Ctrl+N restent
|
||
automatiques. Les preuves, empreintes, logs et patch non commité sont conservés
|
||
dans `build/bootstrap-evidence/` de cette copie privée.
|
||
|
||
**État de la validation historique 1.1.1 : Firefox Release n'avait pas été
|
||
lancé par cette validation VM.** Aucun PASS de login réel, de persistance
|
||
physique ou de déploiement n'était revendiqué. L'artefact 1.1.1 alors construit
|
||
était non signé ; il nécessitait une signature AMO et une installation
|
||
ultérieures. Le test Firefox réel ajouté pour 1.1.2 est décrit ci-dessus. La provenance d'une restauration tardive sans signal reste
|
||
inconnue ; une ancienne valeur jamais encore visible peut toujours arriver entre
|
||
la dernière lecture et l'écriture. Aucune garantie absolue de CAS ou de protection
|
||
d'une identité jamais observée n'est annoncée. Aucune réparation d'UUID perdu,
|
||
réécriture de checkpoint ou modification du PC réel n'a été effectuée.
|