# Changelog - FlowDeck ## v7.5.0 (2026-10-01) — Audit : A29, A42 (partiel) ### Changed - **A29** — `services/publish.py` partagé : les 3 paires publish/unpublish (sharing = front, board, v2) déléguent ; 404 partout (board faisait une mise à jour aveugle), slugify titré unique (board : aléatoire ; v2 : slug fourni conservé), événements centralisés, board gagne le contrôle de session. Les bonus divergents disparaissent (`share_mode='anyone'` pour board, `is_shared=1` pour v2) : le share dialog reste l'unique propriétaire de ces drapeaux, dépublier ne révoque donc pas un partage manuel. Les listings `/users/me` ×2 et collections ×3 restent : contrats versionnés distincts - **A42** — 9 copies de `Path(os.environ.get("FLOWDECK_DATA_DIR", "/data"))` → `settings.data_dir` (property : lecture à chaque accès, les tests monkeypatchent l'env) ; cache Gitea : évacuation des entrées expirées à chaque écriture (il ne pouvait que grandir) ; les 29 `Environment(...)` étaient déjà couverts par A10. **Reste** : client httpx partagé (52 créations, à faire avec un cache par event loop) ### Security - **Byproduct A29** — `GET /api/users/me` (v1) et le contexte Jinja de `/accounts` renvoyaient `SELECT *` sur `users` : **password_hash**, `login_attempts` et `locked_until` exposés → colonnes whitelistées (identiques à la liste v2) ### Tests - `test_publish_service_shared_and_safe` (slug, 404, partage préservé), `test_users_me_no_secret_columns`, `test_gitea_cache_evicts_expired` ## v7.4.0 (2026-10-01) — Audit : A30, A37, A39, A40, A41 ### Changed - **A30** — `require_scope()` est enfin câblé : 69 sites stricts de `api_v2.py` passent par la factory (Bearer + scope en un appel, contrôle manuel supprimé ; les 4 variants `admin|is_admin` restent manuels, ce sont d'autres contrôles) ; 12 top-level morts supprimés (`unsync_block`, `find_referring`, `_b64url`, `strip_markdown`, `format_number`, … — 0 référence app ET tests) - **A37** — CORS : plus de `allow_origins/methods/headers = ["*"]` → origines dérivées de `settings.app_base_url` + localhost/origines d'extension (`allow_origin_regex`), méthodes et entêtes minutés, `allow_credentials=True` explicite ; test `test_cors_no_star` - **A40** — version d'assets à source unique : `{{ asset_version }}` (global Jinja lu au boot depuis le fichier VERSION) ; les littéraux `?v=5.1.1`, `?v=2.4.8`, `?v=6.0.0` de base.html éliminés ; `sw.js` n'existe plus (audit obsolète) ; vendors gardent `?v=` = version de la lib (correct) - **A41** — 91 règles CSS mortes purgées d'`app.css` : **-10 274 octets** (121 618 → 111 344) — scan : classes définies dans app.css et absentes de templates, JS, autres CSS et code Python ### Notes - **A39 (htmx)** — décision « rien » : 32 attributs `hx-*` réels, conversion = refonte du view-switching sans tests E2E ; à reconsidérer avec un test automatisé du view-switch ## v7.3.9 (2026-10-01) — Audit : A26, A33, A34, A35, A36, A43 ### Fixed - **A26** — champ `standalone` mort supprimé (0 lecteur) ; `sqlite:////data/…` ne produit plus un chemin UNC sous Windows ; `.env.example` ne promet plus PostgreSQL (non supporté) ; **raise au boot** si `APP_SECRET_KEY` vaut encore la valeur par défaut (il signe les sessions) - **A33** — rate limit : préfixes manquants ajoutés (`/scim/v2/`, `/workspace/`, `/db/`, plus le non-GET sur `/s/` et `/f/` sans pénaliser la lecture) ; la limite vient de `settings.rate_limit_requests` (60 annoncés, 100 codés en dur) ; clé = `X-Forwarded-For` uniquement derrière un proxy local ; `_store` épuré (croissance mémoire bornée) - **A34** — helper `_spawn()` pour les 10 schedulers : exception loggée + redémarrage après 10 s (ils mouraient en silence) ; 2 `logger.debug` de scheduler passés en `warning` - **A35** — OpenAPI régénéré : 439 → **511 chemins**, `info.version 7.3.9` ; README à jour (était v6.7.0) ; compteur de `API_GUIDE_V6.md` à jour ; titre dupliqué retiré du ROADMAP - **A36** — 4 dépendances mortes purgées de `requirements.txt` (`aiosqlite`, `slowapi`, `loguru`, `packaging` = 0 import) - **A43** — 15 `datetime.utcnow()` dépréciés → `now(UTC).replace(tzinfo=None)` (format ISO naïf identique, zéro changement de comportement) ### Notes - Le drift Python (Docker/CI/README 3.12 vs venv local 3.13) reste ouvert : l'alignement à 3.13 implique un rebuild d'image à valider ## v7.3.8 (2026-10-01) — Audit : A25 (exceptions muettes) + A21 partiel ### Fixed - **A25** — 84 `except Exception: pass/…` deviennent `logger.exception(fn)` (19 fichiers, 63 dans des handlers `async`) : les échecs du pipeline d'événements/webhooks et des écritures sont enfin visibles dans les logs - **A25 (critique)** — plus de `try` autour de `materialize_properties` dans `create_collection_v2` et `apply_db_template_v2` : un échec annule la transaction au lieu de commiter une collection sans schéma - **A21 (partiel)** — `PRAGMA busy_timeout=5000` dans `get_conn()` (le seul point d'entrée des connexions) ; le wrapper async + les 510 call sites synchrones sur l'event loop restent à migrer ### Tests - `test_collection_rollback_when_materialize_fails` → suite **1028/1028** ## v7.3.7 (2026-09-30) — Audit sécurité : A14 (fallback admin agent) ### Fixed - **A14** — `_current_user_id` et `_current_admin` ne retombent plus sur la row `admin` : 401 sans session (les 24 sites de `_current_user_id` + `PATCH/POST /api/agent/providers`) — un anonymous ne pouvait plus orienter le `ping()` du serveur vers un `api_base` interne - `_check_api_base()` sur les 2 routes provider : scheme `http(s)` obligatoire, identifiants dans l'URL refusés (400). Les hôtes privés restent acceptés — le provider par défaut du produit est Ollama `http://localhost:11434/v1` ( commentaire `ponytail:` : fermeture possible via allowlist provider local) - Test `test_agent_providers_require_admin_and_valid_api_base` → suite **1027/1027** ## v7.3.6 (2026-09-30) — Audit sécurité : A19 terminé (aucun préfixe cookie-auth exempt) ### Fixed - **A19 (fin)** — les 46 appels non-GET restants des 5 derniers préfixes (`/api/agent`, `/api/settings`, `/api/local-workspace`, `/api/gitea`, `/api/workspace` + `/api/workspaces`) reçoivent `X-CSRF-Token` (expression cookie en ligne, portée indifférente fonction/Alpine/attribut) ; les 5 préfixes sortent d'`EXCLUDED_PATHS` - Vérification syntaxe : les `