labfy-investigation/docs/architecture/WEB_WORKSPACE_CONTROL.md
fy59 d65f9559e6 chore(checkpoint): preserve local investigation workbench
Preserve the local graph-first investigation stack through macro V1.

Include reviewed source, tests, contracts, documentation and synthetic assets.

Retain the standalone financial V21 test foundation without runtime migration.

Runtime remains V20; operational JobStore remains V3.

Local experimental checkpoint, not a stable release.
2026-09-27 08:34:46 +02:00

116 lines
6.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Poste de travail Web contrôlé — J6 local
> État : `CURRENT` pour le seul espace `SPECIMEN` produit par le lanceur J6.
> Runtime métier : V20 inchangé. JobStore : V1 inchangé. V21 reste indépendante.
## Parcours
Une commande prépare ou reprend un espace synthétique, lance l’API loopback et
son worker possédé, puis affiche l’URL et un code de session éphémère :
```sh
make -j8 tools/local-jobs
make web-workspace WORKSPACE=/tmp/labfy-j6-specimen
```
L’utilisateur ouvre l’URL, saisit le code, sélectionne l’EML ou l’image dans le
graphe et choisit « Analyser les en-têtes » ou « Examiner les métadonnées ».
L’intention est persistée avant la réponse HTTP `202`. Le worker C exécute le
service J3/J4 correspondant, puis C republie atomiquement les snapshots jobs et
métier. Le navigateur les relit sans rechargement de page. Pause, reprise, stop
et annulation sont disponibles dans le panneau Jobs.
## Décision HTTP et bridge
Deux solutions ont été comparées : GNU libmicrohttpd aurait ajouté une
dépendance C et son cycle d’événements à ce prototype ; `http.server` est livré
avec Python mais sa documentation officielle précise qu’il ne convient pas à
la production. J6 retient donc une passerelle Python dédiée et bornée, fondée
sur `ThreadingHTTPServer`, uniquement en loopback et explicitement non destinée
à la production. Elle ajoute toutes les validations nécessaires au périmètre
local au lieu de promouvoir le serveur read-only historique.
Références :
- <https://docs.python.org/3/library/http.server.html>
- <https://www.gnu.org/software/libmicrohttpd/manual/libmicrohttpd.html>
- <https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html>
- <https://www.sqlite.org/isolation.html>
Python n’ouvre ni `Enquete.sqlite` ni `jobs.sqlite`, ne choisit aucun exécutable
et ne lance jamais ExifTool directement. Il appelle `tools/local-jobs` avec une
liste blanche d’opérations et des argv construits par le serveur. Le stdout JSON
machine utilise `labfy.local_jobs.command.v1`; les diagnostics sont sur stderr.
Les analyses longues s’exécutent dans un subprocess hors de la requête HTTP.
Le service C recharge la preuve et son enquête, vérifie son type, son intégrité,
l’applicabilité et la disponibilité du registre, fixe adapter et paramètres,
puis écrit dans le JobStore. Seuls `labfy.capability.eml_headers.v1` et
`labfy.capability.exif_metadata.v1` sont admis. Aucun chemin, SQL, URL, argv,
exécutable, environnement ou paramètre d’outil n’est accepté depuis HTTP.
## Session et sécurité locale
Le serveur écoute exclusivement sur l’autorité annoncée
`127.0.0.1:<port>`. Le code d’amorçage et les secrets de session proviennent de
`secrets`, ne sont pas placés dans une URL et ne sont pas journalisés par les
routes. Après amorçage, le cookie est host-only, `HttpOnly`, `SameSite=Strict`,
`Path=/`, limité à une heure. Il n’a pas l’attribut `Secure`, puisque ce mode
testé utilise HTTP loopback et ne revendique pas TLS.
Lectures opérationnelles et mutations exigent la session. Les mutations exigent
en plus l’`Origin` exact et `X-Labfy-CSRF`. Elles utilisent exclusivement
`POST application/json`, des ensembles de champs exacts et des corps limités à
4 Kio. Host étranger, Origin absent/étranger/null, CSRF absent, type simple,
JSON malformé, champ inconnu, route GET de mutation et capability inconnue sont
refusés. CSP, `nosniff`, `no-store`, `frame-ancestors none`, `X-Frame-Options`
et rendu DOM par `textContent` restent actifs. Il n’existe ni CORS, ni route de
fichier arbitraire, ni fetch distant, ni exposition LAN.
## Idempotence, contrôle et reprise
Le navigateur conserve l’UUID d’intention en `sessionStorage` uniquement tant
que l’admission n’est pas connue. Ce n’est pas un secret. Le cœur dérive de
façon déterministe les UUID de requête et de dérivé ; la contrainte JobStore
rend l’admission transactionnellement idempotente. Même clé et même intention
retrouvent le job ; une autre preuve ou capability produit un conflit. Une
réanalyse volontaire après succès reçoit une nouvelle clé et ne remplace aucun
résultat.
La pause empêche les nouveaux claims. `resume` lève explicitement pause et stop.
Le stop empêche les départs et termine le groupe worker possédé. L’annulation
persistée d’une attente produit `CANCELLED`. Pour un worker actif, le serveur
arrête uniquement son groupe de session puis relance la réconciliation. Au
redémarrage, le cœur inspecte d’abord l’extraction : publication typée présente
→ acquittement sans outil ; absence + annulation → `CANCELLED` ; absence sans
ambiguïté → nouvelle tentative bornée ; incohérence → `RECOVERY_REQUIRED`.
Une simple vérification de replay ne lance donc plus une analyse cachée dans
l’ancienne tentative.
L’EML synchrone reste coopératif aux points définis par son service. Le worker
intercepte l’arrêt et demande au runner de terminer puis récolter la session
isolée d’ExifTool avant de libérer son verrou ; worker et outil ne partagent donc
pas un groupe de processus. J6 ne prétend toujours
pas fournir une sandbox hostile ou une garantie de coupure électrique.
## Contrats de lecture et cohérence
- graphe : `labfy.web_graph.snapshot.v3` ;
- jobs : `labfy.local_jobs.snapshot.v1` ;
- session : `labfy.workspace.session.v1` ;
- contrôle : `labfy.workspace.control.v1` ;
- erreur : `labfy.workspace.error.v1`.
Le polling est borné et non chevauchant : 500 ms pour les jobs, 700 ms pour le
graphe. Chaque publication métier réussie déclenche un export du graphe même si
un autre job échoue. Un export indisponible laisse la dernière vue valide avec
un diagnostic ; il ne change jamais l’état métier du job. Sélection, focus,
zoom, filtres, repli et positions épinglées restent dans l’état du navigateur.
## Limites
Ce poste est une fondation locale synthétique : pas d’enquête réelle, LAN,
TLS, reverse proxy, service permanent, autonomie réseau, recherche OSINT,
worker distribué ou mutation de la V21. Les anciens modes J2–J5 restent
strictement read-only. `ThreadingHTTPServer` n’est pas présenté comme un serveur
Internet ou une frontière de sécurité de production.