# Vault DIR « Spark » — exclusions par vault et lecture seule déclarable ## Objectif Permettre d'exposer dans ObsiGate des répertoires techniques (code du service d'agents, workspaces du compagnon, coquilles d'exécuteurs) sous un vault `DIR` monté en lecture seule — le vault « Spark » — sans jamais rendre visibles leurs zones sensibles (profils et clés des exécuteurs, `.home` des workspaces, `.venv`, etc.). Avant ce chantier, deux manques le bloquaient proprement : 1. Les exclusions n'existaient qu'au niveau de l'indexeur (`IGNORED_DIRS`, global) : la navigation et la lecture montraient le disque tel quel. 2. La lecture seule d'un vault n'était ni déclarable ni affichée : les menus contextuels proposaient renommer / déplacer / supprimer, qui échouaient ensuite côté serveur (montage `:ro`). ## Contrat de configuration (vaults DIR seulement) Lues par `load_vault_config()` (`backend/indexer.py`, boucle `DIR_N`), rangées dans le dict `config` du vault ; les vaults `VAULT` ne sont pas touchés et les défauts (vide / faux) conservent le comportement actuel : | Variable | Forme | Effet | |---|---|---| | `DIR_N_READONLY` | `true` / `false` | Mutations refusées en 403 `read_only` ; `readonly: true` exposé par `/api/vaults` ; l'UI masque les actions d'écriture | | `DIR_N_IGNORED_DIRS` | `.home,.venv` | Noms de dossiers cachés à toute profondeur, en plus du global | | `DIR_N_IGNORED_PATHS` | `Executeurs/*/data,Executeurs/*/.env` | Motifs fnmatch sur le chemin relatif POSIX ; un dossier qui matche cache tout son sous-arbre | Exemple visé pour le vault Spark : ``` DIR_1_NAME=Spark DIR_1_PATH=/vaults/Spark DIR_1_READONLY=true DIR_1_IGNORED_DIRS=.home,.venv,__pycache__,node_modules,.git DIR_1_IGNORED_PATHS=Executeurs/*/data,Executeurs/*/.env,*/compagnon.json ``` (le montage lui-même — volume `:ro`, arborescence assemblée sur l'hôte — relève de la configuration de déploiement, pas d'ObsiGate.) ## Implémentation - **Helper central** : `chemin_exclu(vault_name, rel_path)` et `chemin_exclu_dans_config(config, rel_path)` dans `backend/services/vaults.py` — segment présent dans `ignoredDirs`, ou fnmatch du chemin ou d'un de ses préfixes contre `ignoredPaths`. Tolérant si vault / config absents. `vault_readonly(vault_name)` au même endroit. - **Navigation** : `browse_directory` filtre les items exclus (et leurs compteurs d'enfants) ; naviguer directement dans un chemin exclu répond 404. `list_all_files` et `list_paths` filtrent aussi. - **Lecture** : un chemin exclu est indiscernable d'un chemin inexistant (404, sans détail sur la règle) — `read_raw_file` / `read_file_text` (`backend/services/files.py`), route `GET /api/file/{vault}` et route de téléchargement (`backend/routers/files_read.py`). - **Indexation** : le scan (`_scan_vault`, qui reçoit le `vault_cfg`) élague les dossiers exclus et saute les fichiers exclus, en plus du global `IGNORED_DIRS` inchangé. - **Lecture seule** : `_ensure_writable(root, vault_name)` dans `backend/services/mutations.py` refuse d'abord sur la déclaration (`DIR_N_READONLY`), avant le garde filesystem existant — toutes les mutations du service (créer, éditer, renommer, déplacer, supprimer, upload, xlsx/csv) passent par ce point. - **API / UI** : `VaultInfo.readonly` (`backend/schemas.py`) rempli par `list_accessible_vaults` ; côté frontend, `vaultIsReadonly(nom)` dans `frontend/js/sidebar.js` alimente `ContextMenuManager`, `attachTreeItemActionButton` et `attachTreeItemLongPress` pour le vault et ses dossiers / fichiers (aucun autre fichier JS touché). ## Tests `tests/test_spark_vault_dir.py` : parsing des trois variables, helper `chemin_exclu` (noms et motifs), navigation filtrée, lectures exclues en 404 (vue fichier, raw, téléchargement), fichier non exclu lisible, indexation filtrée, mutation refusée en 403 sur vault readonly et `/api/vaults` exposant `readonly=true`.