feat: vault DIR — exclusions par vault et lecture seule déclarable (vault Spark)

This commit is contained in:
Spark
2026-11-03 14:09:12 -05:00
committed by bruno
parent 52dcbf3607
commit 3ba8b7b667
13 changed files with 545 additions and 46 deletions
+44 -4
View File
@@ -97,6 +97,13 @@ def load_vault_config() -> dict[str, dict[str, Any]]:
Also reads optional configuration:
- VAULT_N_ATTACHMENTS_PATH: relative path to attachments folder
- VAULT_N_SCAN_ATTACHMENTS: "true"/"false" to enable/disable scanning
- DIR_N_READONLY: "true"/"false" — vault DIR déclaré en lecture seule
(les mutations sont refusées en 403, l'UI masque les actions d'écriture)
- DIR_N_IGNORED_DIRS: noms de dossiers ignorés pour ce vault, séparés
par des virgules (toute profondeur, en plus du global IGNORED_DIRS)
- DIR_N_IGNORED_PATHS: motifs fnmatch sur le chemin relatif POSIX dans
le vault, séparés par des virgules (un dossier qui matche cache tout
son sous-arbre) — navigation, lecture et indexation les respectent
Returns:
Dict mapping vault names to configuration dicts with keys:
@@ -104,6 +111,9 @@ def load_vault_config() -> dict[str, dict[str, Any]]:
- attachmentsPath: relative attachments folder (optional)
- scanAttachmentsOnStartup: boolean (default True)
- type: "VAULT" or "DIR"
- readonly: boolean (DIR only, default False)
- ignoredDirs: set of directory names (DIR only, default empty)
- ignoredPaths: list of fnmatch patterns (DIR only, default empty)
"""
vaults: dict[str, dict[str, Any]] = {}
n = 1
@@ -131,12 +141,23 @@ def load_vault_config() -> dict[str, dict[str, Any]]:
path = os.environ.get(f"DIR_{n}_PATH")
if not name or not path:
break
readonly = os.environ.get(f"DIR_{n}_READONLY", "false").lower() == "true"
ignored_dirs = {
d.strip() for d in os.environ.get(f"DIR_{n}_IGNORED_DIRS", "").split(",") if d.strip()
}
ignored_paths = [
p.strip() for p in os.environ.get(f"DIR_{n}_IGNORED_PATHS", "").split(",") if p.strip()
]
vaults[name] = {
"path": path,
"attachmentsPath": None,
"scanAttachmentsOnStartup": False,
"type": "DIR"
"type": "DIR",
"readonly": readonly,
"ignoredDirs": ignored_dirs,
"ignoredPaths": ignored_paths,
}
n += 1
@@ -509,7 +530,8 @@ def _scan_vault(
Args:
vault_name: Display name of the vault.
vault_path: Absolute filesystem path to the vault root.
vault_cfg: Optional vault configuration dict (unused for indexing, kept for compatibility).
vault_cfg: Optional vault configuration dict — ses exclusions
(``ignoredDirs`` / ``ignoredPaths``) sont appliquées au scan.
previous_files: Optional ``{relative_path: file_info}`` snapshot from a
previous scan used for differential reuse.
@@ -529,6 +551,16 @@ def _scan_vault(
root_resolved = vault_root.resolve(strict=False)
# Exclusions propres au vault (DIR_N_IGNORED_DIRS / DIR_N_IGNORED_PATHS) :
# elles s'ajoutent au global IGNORED_DIRS, sans le modifier. Le cfg est
# celui passé par l'appelant — l'index global ne contient pas encore ce
# vault pendant son premier scan, chemin_exclu() (qui lit l'index) ne
# peut donc pas servir ici.
from backend.services.vaults import chemin_exclu_dans_config
cfg = vault_cfg or {}
ignored_names = IGNORED_DIRS | set(cfg.get("ignoredDirs") or [])
# BUG-032: walk without following symlinks and refuse any symlink that
# escapes the vault root, so external data can never be indexed/exposed.
for dirpath, dirnames, filenames in os.walk(vault_root, followlinks=False):
@@ -537,7 +569,11 @@ def _scan_vault(
# Prune ignored and symlinked directories in place (no recursion).
dirnames[:] = [
d for d in dirnames
if d not in IGNORED_DIRS and not (current_dir / d).is_symlink()
if d not in ignored_names
and not (current_dir / d).is_symlink()
and not chemin_exclu_dans_config(
cfg, str((current_dir / d).relative_to(vault_root)).replace("\\", "/")
)
]
for d in dirnames:
@@ -565,6 +601,10 @@ def _scan_vault(
rel_path_str = str(fpath.relative_to(vault_root)).replace("\\", "/")
# Exclusion propre au vault (motif de chemin, ex. un .env ciblé).
if chemin_exclu_dans_config(cfg, rel_path_str):
continue
ext = fpath.suffix.lower()
# Also match extensionless files named like Dockerfile, Makefile
basename_lower = fpath.name.lower()