From 8e073438a33441937b1f79364361c9390a362aa4 Mon Sep 17 00:00:00 2001 From: Spark Date: Mon, 26 Oct 2026 00:06:15 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20URLs=20s=C5=93urs=20d=C3=A9riv=C3=A9es?= =?UTF-8?q?=20de=20l'environnement=20de=20l'agent=20=E2=80=94=20le=20menu?= =?UTF-8?q?=20inter-apps=20ne=20pointe=20plus=20la=20prod=20depuis=20la=20?= =?UTF-8?q?dev=20BUG-120?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 7 ++- CHANGELOG.md | 28 ++++++++- README.fr.md | 6 +- README.md | 6 +- VERSION | 2 +- backend/draco_config.py | 74 ++++++++++++++++++++++-- desktop/Cargo.lock | 2 +- desktop/Cargo.toml | 2 +- desktop/tauri.conf.json | 2 +- docs/ISSUES_TODOLIST.md | 2 + docs/ROADMAP.md | 3 +- docs/features/devprod-urls-config-223.md | 16 +++++ package.json | 2 +- tests/test_draco_config.py | 73 ++++++++++++++++++++++- 14 files changed, 204 insertions(+), 21 deletions(-) diff --git a/.env.example b/.env.example index afb7472..485b65f 100644 --- a/.env.example +++ b/.env.example @@ -108,8 +108,11 @@ DEEPSEEK_MODEL=deepseek-chat # ── Fusion / dev-prod — URL publiques de l'écosystème (#223) ── # Aucun nom public n'est figé dans le code : ces variables portent les # URL des autres applications. Valeurs par défaut (si absentes) = noms -# OFFICIELS de la production. L'environnement dev déclare ses noms en -# -dev dans son propre .env uniquement, par exemple : +# OFFICIELS de la production. Depuis BUG-120, les URL sœurs se dérivent +# de l'hôte de l'agent (agent.dracodev.net, lu via +# AGENT_EVENEMENTS_URL / DRACO_AGENT_URL) : en dev, rien d'autre à +# déclarer pour obtenir les noms en -dev. Une variable explicite +# reste prioritaire sur la dérivation, par exemple : # DRACO_TIKTAK_URL=https://tiktak-dev.dracodev.net # DRACO_AGENT_URL=https://agent.dracodev.net # DRACO_OBSIGATE_URL=https://og.dracodev.net diff --git a/CHANGELOG.md b/CHANGELOG.md index b98cf14..49a83bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ Format basé sur [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/), et [Semantic Versioning](https://semver.org/spec/v2.0.0.html). > **En cours de développement** : les changements à venir sont listés dans la section -> [Unreleased](#unreleased). La dernière version livrée est **2.78.1**. +> [Unreleased](#unreleased). La dernière version livrée est **2.78.2**. --- @@ -14,6 +14,32 @@ et [Semantic Versioning](https://semver.org/spec/v2.0.0.html). --- +## [2.78.2] — 2026-10-26 + +### Corrigé + +- **Menu inter-apps : les URL sœurs suivaient l'environnement de + l'agent (BUG-120, suite de #223)** — sur la dev + (`og-dev.dracodev.net`), le `window.DRACO_URLS` servi donnait + l'agent en `-dev` mais les applications sœurs (ObsiGate, Flowdeck, + TikTak, Kanbante) en noms **prod** : seule l'URL de l'agent avait + une dérivation de repli, et le `.env` dev ne déclare pas les quatre + `DRACO__URL`. Résolution d'une URL sœur dans + `backend/draco_config.py`, inchangée dans son contrat : (1) la + variable explicite prime ; (2) à défaut, **dérivation + d'environnement** depuis l'hôte de l'agent tel qu'ObsiGate le + connaît déjà (`agent_url()`, qui lit `AGENT_EVENEMENTS_URL`) — si + cet hôte est de la forme `agent.dracodev.net`, l'URL sœur + devient `https://.dracodev.net` (`og`, `flowdeck`, + `tiktak`, `kanbante`) ; hôte hors motif (autre domaine, LAN, IP, + port) → **aucune dérivation**, jamais de lien deviné ; (3) défaut + officiel. L'URL Gitea ne change pas. Aucune variable supplémentaire + à déclarer en dev. Tests `tests/test_draco_config.py` étendus + (dérivation `-dev`, priorité de l'explicite, hôtes hors motif, + non-régression sans env). Redéploiement Docker requis. + +--- + ## [2.78.1] — 2026-10-25 ### Corrigé diff --git a/README.fr.md b/README.fr.md index c8b539f..1676273 100644 --- a/README.fr.md +++ b/README.fr.md @@ -4,7 +4,7 @@ **Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive. -[![Version](https://img.shields.io/badge/Version-2.78.1-blue.svg)]() +[![Version](https://img.shields.io/badge/Version-2.78.2-blue.svg)]() [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/) [![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/) @@ -977,8 +977,8 @@ Ce projet est sous licence **MIT** — voir le fichier [LICENSE](LICENSE) pour l ## 📝 Changelog -Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.78.1). +Consultez le [CHANGELOG.md](./CHANGELOG.md) pour l'historique complet de toutes les versions (v1.0.0 → v2.78.2). --- -*Projet : ObsiGate | Version : 2.78.1 | Dernière mise à jour : Septembre 2026* +*Projet : ObsiGate | Version : 2.78.2 | Dernière mise à jour : Septembre 2026* diff --git a/README.md b/README.md index 29a1f36..f0cc170 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ **Ultra-light web gateway for your Obsidian vaults** — Access, browse, and search all your Obsidian notes from any device via a modern, responsive web interface. -[![Version](https://img.shields.io/badge/Version-2.78.1-blue.svg)]() +[![Version](https://img.shields.io/badge/Version-2.78.2-blue.svg)]() [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Docker](https://img.shields.io/badge/Docker-Ready-blue.svg)](https://www.docker.com/) [![Python](https://img.shields.io/badge/Python-3.11+-green.svg)](https://www.python.org/) @@ -1152,8 +1152,8 @@ This project is licensed under the **MIT License** - see the [LICENSE](LICENSE) ## 📝 Changelog -See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.78.1). +See [CHANGELOG.md](./CHANGELOG.md) for the complete version history (v1.0.0 → v2.78.2). --- -*Project: ObsiGate | Version: 2.78.1 | Last updated: September 2026* +*Project: ObsiGate | Version: 2.78.2 | Last updated: September 2026* diff --git a/VERSION b/VERSION index 3f7e4a1..cd46641 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.78.1 +2.78.2 diff --git a/backend/draco_config.py b/backend/draco_config.py index 2428a36..eae1e65 100644 --- a/backend/draco_config.py +++ b/backend/draco_config.py @@ -22,6 +22,16 @@ Variables (voir ``.env.example``) : distincte de ``OBSIGATE_GITEA_URL`` (source connectée de l'assistant, réglable depuis l'UI, pas une URL d'affichage). +Résolution d'une URL sœur (BUG-120) : la variable explicite prime ; +à défaut, l'**environnement est dérivé de l'hôte de l'agent** tel +qu'ObsiGate le connaît déjà (:func:`agent_url`) — si cet hôte est de +la forme ``agent.dracodev.net`` (``-dev`` en dev, vide en +prod), l'URL sœur est ``https://.dracodev.net``. Un +hôte hors motif (autre domaine, LAN, IP, port) ne dérive **rien** : +on ne fabrique jamais un lien deviné, le défaut officiel s'applique. +L'installation pointe donc les bons liens selon son environnement +sans déclarer les quatre ``DRACO__URL``. + Ce module ne lit l'environnement qu'à l'appel (jamais à l'import) : les tests et un changement de ``.env`` suivi d'un redémarrage prennent effet sans état figé. @@ -30,6 +40,7 @@ prennent effet sans état figé. from __future__ import annotations import os +import re from urllib.parse import urlparse # Défauts officiels (production future) — jamais de nom en ``-dev`` ici. @@ -51,6 +62,21 @@ _VARIABLES: dict[str, str] = { "gitea": "DRACO_GITEA_URL", } +# Slugs d'hôte des applications sœurs pour la dérivation +# d'environnement (BUG-120) : premier label de ``.dracodev.net`` +# (et de ``-dev.dracodev.net`` en dev). Gitea est **exclue** : +# son URL ne se dérive jamais. +_SLUGS_HOTE: dict[str, str] = { + "obsigate": "og", + "flowdeck": "flowdeck", + "tiktak": "tiktak", + "kanbante": "kanbante", +} + +# Premier label de l'hôte de l'agent : ``agent`` suivi d'un suffixe +# d'environnement éventuel (``-dev`` en dev, vide en prod). +_MOTIF_AGENT = re.compile(r"^agent(-[a-z0-9-]*)?$") + def _nettoyer(valeur: str) -> str: return valeur.strip().rstrip("/") @@ -87,12 +113,52 @@ def agent_origine() -> str: return origine(agent_url()) or DEFAUTS["agent"] +def _suffixe_environnement() -> str | None: + """Suffixe d'environnement déduit de l'hôte de l'agent (BUG-120). + + Lit l'hôte de :func:`agent_url` — l'origine de l'agent telle + qu'ObsiGate la connaît déjà (``DRACO_AGENT_URL``, à défaut + ``AGENT_EVENEMENTS_URL``). Si cet hôte est exactement de la forme + ``agent.dracodev.net`` sans port, retourne ``SUFFIXE`` + (``-dev`` en dev, ``""`` en prod). Sinon (autre domaine, LAN, IP, + port exotique) retourne ``None`` : aucune dérivation, on ne + fabrique jamais un lien deviné. + """ + u = urlparse(agent_url()) + hote = (u.hostname or "").lower() + labels = hote.split(".") + if len(labels) != 3 or labels[1:] != ["dracodev", "net"]: + return None + try: + if u.port is not None: + return None + except ValueError: + return None + motif = _MOTIF_AGENT.match(labels[0]) + if motif is None: + return None + return motif.group(1) or "" + + def app_url(app: str) -> str: - """Base publique d'une application de l'écosystème (sans ``/`` final).""" + """Base publique d'une application de l'écosystème (sans ``/`` final). + + Résolution d'une URL sœur (BUG-120) : ``DRACO__URL`` + explicite d'abord ; à défaut, dérivation d'environnement depuis + l'hôte de l'agent (:func:`_suffixe_environnement`) ; à défaut, + le défaut officiel. L'URL Gitea ne se dérive jamais. + """ if app == "agent": return agent_url() variable = _VARIABLES[app] - return _nettoyer(os.environ.get(variable, "")) or DEFAUTS[app] + explicite = _nettoyer(os.environ.get(variable, "")) + if explicite: + return explicite + if app in _SLUGS_HOTE: + suffixe = _suffixe_environnement() + if suffixe is not None: + return f"https://{_SLUGS_HOTE[app]}{suffixe}.dracodev.net" + return DEFAUTS[app] def app_hote(app: str) -> str: @@ -105,8 +171,8 @@ def hotes_reconnus() -> dict[str, str]: Contient **toujours** les hôtes officiels (un lien collé depuis la production reste reconnaissable en dev, et réciproquement) **plus** - l'hôte configuré de chaque application (les noms en ``-dev`` du - poste quand ils sont déclarés dans son ``.env``). + l'hôte résolu de chaque application (les noms en ``-dev`` du poste, + déclarés dans son ``.env`` ou dérivés de l'hôte de l'agent). """ hotes: dict[str, str] = {} for app, defaut in DEFAUTS.items(): diff --git a/desktop/Cargo.lock b/desktop/Cargo.lock index 51b53a0..fa4e742 100644 --- a/desktop/Cargo.lock +++ b/desktop/Cargo.lock @@ -2626,7 +2626,7 @@ dependencies = [ [[package]] name = "obsigate-desktop" -version = "2.78.1" +version = "2.78.2" dependencies = [ "chrono", "env_logger", diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index 86db371..6e99ce6 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "obsigate-desktop" -version = "2.78.1" +version = "2.78.2" description = "ObsiGate Desktop — Porte d'entrée native pour vos vaults Obsidian" authors = ["Bruno Charest"] edition = "2021" diff --git a/desktop/tauri.conf.json b/desktop/tauri.conf.json index a68d823..152b222 100644 --- a/desktop/tauri.conf.json +++ b/desktop/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://raw.githubusercontent.com/nicedoc/obsigate/main/desktop/tauri.conf.schema.json", "productName": "ObsiGate", - "version": "2.78.1", + "version": "2.78.2", "identifier": "com.obsigate.desktop", "build": { "frontendDist": "../frontend", diff --git a/docs/ISSUES_TODOLIST.md b/docs/ISSUES_TODOLIST.md index a1449a5..4e0fb2d 100644 --- a/docs/ISSUES_TODOLIST.md +++ b/docs/ISSUES_TODOLIST.md @@ -226,6 +226,7 @@ Avant de corriger quoi que ce soit, un agent IA doit : | *BUG-116* | Éditeur Forge : après insertion d'un tableau (ex. 4 lignes), la textarea reste d'une seule ligne au lieu de s'agrandir | 🟢 corrigé | P2 | 📱 frontend / éditeur | IA | `frontend/editor-poc.html` (`insertAtCursor`, `wrapSelection`) | Insérer un tableau via le menu : le contenu contient bien les nouvelles lignes mais la hauteur de la textarea ne change pas | Les mutations programmatiques de `ta.value` ne déclenchent pas l'événement `input` — seul handler qui rappelait `autoHeight()`/`updateLineNumbers()` (les handlers clavier, eux, les appelaient à la main). Ajout des deux appels dans `insertAtCursor` et `wrapSelection`, les deux points de passage de toutes les insertions (tableau, code, mermaid, checklist, gras multi-ligne) | Vérifié en Playwright sur l'instance E2E (qi → Table, 4 lignes, sur doc d'une ligne) : hauteur 51 → 189 px, 8 lignes rendues, gutter aligné | ### TODOs techniques (améliorations / nouvelles tâches) | *BUG-117* | Drag & drop des fichiers dans la sidebar : le déplacement d'un répertoire à un autre ne fait plus rien, sans message | 🟢 corrigé | P1 | 📱 frontend / sidebar | IA | `frontend/js/dragdrop.js` (`onDrop`) | Glisser un fichier sur un dossier : aucune requête `/api/move`, aucun toast | La détection `isExternalFiles` dans `onDrop` (`items.length > 0`, ajoutée par #89) comptait aussi les formats string du drag interne (`tree:vault/path/type` posés par `setData` au `dragstart`) : tout déplacement partait dans la branche upload où rien ne passe le filtre `kind === 'file'` → drop avalé silencieusement. Corrigé à la racine : `dragState` (interne) prioritaire + items réellement fichiers. Livré avec #201 : menu contextuel Déplacer/Dupliquer réutilisant `promptTargetFolder`, emplacement actuel pré-sélectionné | Vérifié Playwright instance E2E : glisser `note1.md` → `d1` = toast « Moved file to d1 » + chemin déplacé ; 4 tests `TestDuplicateApi` | | *BUG-119* | Panneau Spark : le « mode console » (surimpression iframe de la console du service d'agents) est bloqué par le navigateur (« This content is blocked ») | 🟢 corrigé | P1 | ⚙️ backend | IA | `backend/main.py` (`SecurityHeadersMiddleware`) | Panneau Spark → « Console ⤢ » : l'iframe reste sur la page d'erreur du navigateur | `frame-src 'self' blob: https://agent.dracodev.net` dans la CSP servie ; test `tests/test_security_headers.py::test_csp_frame_src_allows_agent_console` | Cause : `frame-src` n'autorisait pas l'origine du service d'agents, pourtant déjà présente dans `connect-src` (le panneau texte fonctionnait). Aucune autre directive modifiée. Redéploiement Docker requis | +| *BUG-120* | Menu inter-apps : sur l'environnement dev, les URL sœurs servies (`window.DRACO_URLS` : ObsiGate, Flowdeck, TikTak, Kanbante) restent en noms **prod** alors que l'URL de l'agent est bien en `-dev` | 🟢 corrigé | P1 | ⚙️ backend | IA | `backend/draco_config.py` (`app_url`, `_suffixe_environnement`) | Sur og-dev : `window.DRACO_URLS` donne `agent: https://agent-dev.dracodev.net` mais `tiktak: https://tiktak.dracodev.net` (prod) — le `.env` dev ne déclare pas les quatre `DRACO__URL` | `backend/draco_config.py` : résolution sœur en 3 temps — explicite, puis **dérivation d'environnement** depuis l'hôte de `agent_url()` (`agent.dracodev.net` → `https://.dracodev.net`, slugs `og`/`flowdeck`/`tiktak`/`kanbante` ; hors motif → rien), puis défaut officiel ; Gitea exclue. Tests `tests/test_draco_config.py` (+9 : dérivation `-dev` via `AGENT_EVENEMENTS_URL` et via `DRACO_AGENT_URL`, priorité de l'explicite, 5 hôtes hors motif, suffixe vide, injection) | Suite de #223 : seule l'URL de l'agent avait une dérivation de repli (`AGENT_EVENEMENTS_URL`) ; les sœurs retombaient sur les défauts officiels. Correctif de code demandé par Bruno : l'environnement se déduit de l'agent déjà configuré, sans variable supplémentaire. Redéploiement Docker requis | | # | Titre | Statut | Priorité | Scope | Assigné | Zone (fichier) | Cmd de repro | Correctif / Commit | Notes | |---|---|---|---|---|---|---|---|---|---| @@ -359,6 +360,7 @@ Avant de corriger quoi que ce soit, un agent IA doit : | 2026-10-15 | BUG-117 + #201 | Correction + feature | `frontend/js/dragdrop.js`, `frontend/js/ui.js`, `frontend/js/upload.js`, `frontend/locales/fr.json`, `frontend/locales/en.json`, `backend/services/mutations.py`, `backend/routers/files_write.py`, `backend/schemas.py`, `tests/test_api_main.py`, `CHANGELOG.md`, `docs/ROADMAP.md`, `docs/ISSUES_TODOLIST.md` | **BUG-117 — le drag & drop interne de la sidebar était mort en silence.** `onDrop` classait tout dépôt « externe » dès que `dataTransfer.items` était non vide — or un glisser interne dépose 4 formats string (`tree:*`) : chaque déplacement partait dans la branche upload où le filtre `kind === 'file'` ne trouvait rien — aucun appel API, aucun toast. Détection corrigée (vrais fichiers seulement, `dragState` prioritaire). Livré avec **#201 : menu contextuel Déplacer/Dupliquer** (fichiers et dossiers), modal `promptTargetFolder` réutilisé (+ `opts`, emplacement actuel pré-sélectionné), endpoint `POST /api/duplicate/{vault}` (`duplicate_path`, index incrémental, SSE/webhooks, refus inside-self), i18n FR/EN, 4 tests `TestDuplicateApi` + validation Playwright E2E complète | 🟢 corrigé/livré (en attente vérif utilisateur) | | 2026-10-09 | #194 | Correction (isolation) | `backend/auth/middleware.py`, `backend/services/recent.py`, `backend/services/search.py`, `backend/routers/search.py`, `backend/routers/config.py`, `backend/routers/conflicts.py`, `backend/routers/files_read.py`, `backend/routers/history.py`, `backend/push.py`, `backend/user_home.py`, `tests/test_user_home.py`, `tests/test_auth_api.py`, `docs/features/user-home-194.md`, `CHANGELOG.md` | **#194 — l'admin voyait le home de chaque utilisateur** (retour utilisateur) : `vaults: ["*"]` ouvrait **tous** les vaults, donc `home-` compris. Correctif à la racine : `check_vault_access` exige un octroi **explicite** pour tout vault `home-*` (nouveau `is_home_vault()`), et les filtres « * » écrits en dur dans les routes ont été remplacés par cet appel unique — dashboard (`routers/config.py`), conflits, liens retour, favoris, abonnements push (`/api/search/replace` utilisait déjà `is_vault_allowed`). `/api/search` filtre désormais les bruts **avant** pagination via `search_vaults(is_allowed=…)` (sinon notes personnelles d'un autre compte dans les résultats). `_grant` n'écarte plus les comptes « * » → l'admin reçoit son propre `home-admin` (auto-réparé au démarrage). Vérifié : `test_user_home.py` 12→**14/14** (dont « * » sans homes + filtrage recherche), `test_auth_api.py` 2/2 (admin ne voit **plus** `home-alice`, alice voit le sien), suite **1705 passed / 2 skipped**, ruff backend 0, mypy 114 fichiers 0, **live Playwright instance 2020** : arborescence admin = `home-admin, TestDir, TestVault` (plus de `home-bruno`), `svg.lucide-home` rendu. Reste ouvert (fuite pré-existante, hors périmètre) : `/api/search/advanced`, `/api/tags`, `/api/tags/suggest`, `/api/tree-search`, `/api/suggest` ne filtrent pas par vault accessible | ✅ livré (en attente vérif utilisateur) | | 2026-10-23 | BUG-119 | Correction | `backend/main.py`, `tests/test_security_headers.py`, `CHANGELOG.md`, `docs/ISSUES_TODOLIST.md`, `docs/ROADMAP.md` | **BUG-119 — le « mode console » du panneau Spark V3 (#222) était bloqué par le navigateur** (« This content is blocked ») : la console du service d'agents s'affiche dans une iframe `https://agent.dracodev.net`, mais la CSP servie par `SecurityHeadersMiddleware` limitait `frame-src` à `'self' blob:` — seul le panneau texte passait (`connect-src` autorisait déjà l'origine). Correctif strict : `frame-src 'self' blob: https://agent.dracodev.net`, toutes les autres directives inchangées à l'identique. Test de non-régression `test_csp_frame_src_allows_agent_console` (la CSP servie est analysée et la liste exacte des sources `frame-src` verrouillée). Vérifié : 100 passed (security_headers + csp_nonce + security_hardening + draco_barre + version), ruff propre sur les fichiers touchés (51 erreurs préexistantes ailleurs, inchangées), mypy 0 (120 fichiers) | 🟢 corrigé (en attente vérif utilisateur) | +| 2026-10-25 | BUG-120 (suite #223) | Correction | `backend/draco_config.py`, `tests/test_draco_config.py`, `.env.example`, `CHANGELOG.md`, `docs/ISSUES_TODOLIST.md`, `docs/ROADMAP.md`, `docs/features/devprod-urls-config-223.md` | **BUG-120 — le menu inter-apps de la dev pointait la prod** : `window.DRACO_URLS` servait l'agent en `-dev` mais les quatre sœurs en noms officiels, le `.env` dev ne déclarant pas les `DRACO__URL`. Correctif dans `draco_config.app_url()` : après l'explicite, **dérivation d'environnement** — hôte de `agent_url()` de la forme `agent.dracodev.net` sans port → sœur en `.dracodev.net` ; hors motif (autre domaine, LAN, IP, port) aucune dérivation ; Gitea jamais dérivée. Vérifié : `test_draco_config.py` 16 passed, suite complète **1668 passed / 9 skipped**, ruff 0 et mypy 0 sur les fichiers touchés (51 erreurs ruff préexistantes ailleurs, inchangées) | 🟢 corrigé (en attente vérif utilisateur) | --- diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 394c231..c9f3601 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -1,6 +1,6 @@ # ObsiGate — Roadmap -> **Version :** 2.78.1 | **Dernière mise à jour :** 2026-10-25 +> **Version :** 2.78.2 | **Dernière mise à jour :** 2026-10-26 > **Ce fichier ne contient que le travail à venir** (🔵 En cours + ⚪ Backlog) et un index compact > vers les fonctionnalités livrées. > - **Méthode de livraison à appliquer pour toute tâche : [DELIVERY_WORKFLOW.md](./DELIVERY_WORKFLOW.md)** @@ -1149,6 +1149,7 @@ | 168 | Assistant IA — notifications externes Discord / Telegram / SMTP / webhook (déclencheurs, secrets hors config, SSRF-safe) | Unreleased | [features/agent-phase4-166-168-170.md](./features/agent-phase4-166-168-170.md) | | 170 | Assistant IA — tâches planifiées type cron (create/append/notify, interval/daily/once, tick 60 s, échec notifié) | Unreleased | [features/agent-phase4-166-168-170.md](./features/agent-phase4-166-168-170.md) | | BUG-119 | Panneau Spark — « mode console » bloqué par la CSP (`frame-src` sans l'origine du service d'agents) | 2.77.1 | [ISSUES_TODOLIST.md](./ISSUES_TODOLIST.md) | +| BUG-120 | Menu inter-apps — les URL sœurs (`window.DRACO_URLS`) dérivent l'environnement de l'hôte de l'agent (`-dev` en dev), explicite prioritaire, Gitea inchangée (suite de #223) | 2.78.2 | [ISSUES_TODOLIST.md](./ISSUES_TODOLIST.md) | --- diff --git a/docs/features/devprod-urls-config-223.md b/docs/features/devprod-urls-config-223.md index facc699..26f17b6 100644 --- a/docs/features/devprod-urls-config-223.md +++ b/docs/features/devprod-urls-config-223.md @@ -67,6 +67,22 @@ nonce CSP par le mécanisme existant) dans `index.html` au service. `/static/*`, et service worker **network-first** pour le code — aucun cache-busting par empreinte n'existe (revalidation systématique). +## Correctif BUG-120 (2026-10-25) — dérivation d'environnement des sœurs + +Constat sur la dev : le `.env` du poste ne déclare que l'agent (via +`AGENT_EVENEMENTS_URL`) ; les quatre `DRACO__URL` absentes +retombaient sur les défauts prod dans `window.DRACO_URLS` — le menu +inter-apps pointait la prod depuis la dev. La résolution d'une URL +sœur (`backend/draco_config.py::app_url`) gagne une étape +intermédiaire, entre l'explicite et le défaut officiel : +**dérivation depuis l'hôte de l'agent** (`agent_url()`) — hôte de la +forme `agent.dracodev.net` sans port → sœur = +`https://.dracodev.net` (slugs : `og`, `flowdeck`, +`tiktak`, `kanbante`). Hôte hors motif (autre domaine, LAN, IP, +port) → aucune dérivation. Gitea exclue. Le dev n'a plus quatre +variables à déclarer ; un `.env` qui les déclare déjà est inchangé +(l'explicite prime). + ## Restes assumés - Suffixe SSO `dracodev.net` (`draco_session.validate_return_to`, diff --git a/package.json b/package.json index 90769c4..190fdae 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsigate", - "version": "2.78.1", + "version": "2.78.2", "description": "**Porte d'entrée web ultra-léger pour vos vaults Obsidian** — Accédez, naviguez et recherchez dans toutes vos notes Obsidian depuis n'importe quel appareil via une interface web moderne et responsive.", "main": "patch.js", "directories": { diff --git a/tests/test_draco_config.py b/tests/test_draco_config.py index 26f4bda..99bae2c 100644 --- a/tests/test_draco_config.py +++ b/tests/test_draco_config.py @@ -53,6 +53,70 @@ def test_agent_explicite_prime(env_propre): assert draco_config.agent_url() == "https://agent.example.net" +# ------------------------------------------------- BUG-120 : dérivation sœurs + +def test_soeurs_derivees_depuis_agent_dev(env_propre): + """BUG-120 : avec **seul** ``AGENT_EVENEMENTS_URL`` en ``-dev`` + posé (aucune ``DRACO__URL``), les URL sœurs servies suivent + l'environnement de l'agent ; Gitea ne bouge jamais.""" + env_propre.setenv( + "AGENT_EVENEMENTS_URL", "https://agent-dev.dracodev.net/api/v1/evenements") + urls = draco_config.urls_publiques() + assert urls["agent"] == "https://agent-dev.dracodev.net" + assert urls["obsigate"] == "https://og-dev.dracodev.net" + assert urls["flowdeck"] == "https://flowdeck-dev.dracodev.net" + assert urls["tiktak"] == "https://tiktak-dev.dracodev.net" + assert urls["kanbante"] == "https://kanbante-dev.dracodev.net" + assert urls["gitea"] == "https://git.dracodev.net" + + +def test_soeurs_derivees_depuis_draco_agent_url(env_propre): + """BUG-120 : la dérivation lit l'agent via ``agent_url()`` — un + ``DRACO_AGENT_URL`` en ``-dev`` suffit aussi.""" + env_propre.setenv("DRACO_AGENT_URL", "https://agent-dev.dracodev.net") + assert draco_config.app_url("tiktak") == "https://tiktak-dev.dracodev.net" + assert draco_config.app_url("obsigate") == "https://og-dev.dracodev.net" + + +def test_soeur_explicite_prime_sur_derivation(env_propre): + """BUG-120 : une ``DRACO__URL`` explicite gagne toujours sur + la dérivation d'environnement ; les autres sœurs dérivent.""" + env_propre.setenv( + "AGENT_EVENEMENTS_URL", "https://agent-dev.dracodev.net/api/v1/evenements") + env_propre.setenv("DRACO_TIKTAK_URL", "https://taches.example.net/") + assert draco_config.app_url("tiktak") == "https://taches.example.net" + assert draco_config.app_url("flowdeck") == "https://flowdeck-dev.dracodev.net" + assert draco_config.app_url("kanbante") == "https://kanbante-dev.dracodev.net" + + +@pytest.mark.parametrize("evenements", [ + "https://agent.example.net/api/v1/evenements", # autre domaine + "https://agent-dev.example.com/api/v1/evenements", # suffixe hors dracodev.net + "http://deepin.lab.home:8790/api/v1/evenements", # nom LAN + port + "http://192.168.30.101:8790/api/v1/evenements", # IP + port + "https://agent-dev.dracodev.net:8790/api/v1/evenements", # port exotique +]) +def test_agent_hors_motif_aucune_derivation(env_propre, evenements): + """BUG-120 : hôte d'agent hors motif ``agent.dracodev.net`` + → **aucune dérivation** (jamais de lien deviné), défauts officiels.""" + env_propre.setenv("AGENT_EVENEMENTS_URL", evenements) + urls = draco_config.urls_publiques() + assert urls["obsigate"] == "https://og.dracodev.net" + assert urls["flowdeck"] == "https://flowdeck.dracodev.net" + assert urls["tiktak"] == "https://tiktak.dracodev.net" + assert urls["kanbante"] == "https://kanbante.dracodev.net" + assert urls["gitea"] == "https://git.dracodev.net" + + +def test_agent_prod_suffixe_vide_defauts_officiels(env_propre): + """BUG-120 : hôte d'agent prod (suffixe vide) → les sœurs dérivées + sont exactement les défauts officiels.""" + env_propre.setenv( + "AGENT_EVENEMENTS_URL", "https://agent.dracodev.net/api/v1/evenements") + for app, defaut in draco_config.DEFAUTS.items(): + assert draco_config.app_url(app) == defaut + + def test_journal_url_suit_la_config(env_propre): from backend import agent_journal @@ -106,10 +170,15 @@ def test_url_affichage_reconstruite_depuis_config(env_propre, tmp_path): # ------------------------------------------------------------ injection hôte def test_index_injecte_draco_urls(client, env_propre): - """L'hôte sert index.html avec window.DRACO_URLS issu de sa config.""" + """L'hôte sert index.html avec window.DRACO_URLS issu de sa config. + + BUG-120 : l'agent en ``-dev`` entraîne les sœurs en ``-dev`` + (dérivation d'environnement) — avant, ``tiktak`` restait en prod. + """ env_propre.setenv("DRACO_AGENT_URL", "https://agent-dev.dracodev.net") resp = client.get("/") assert resp.status_code == 200 assert "window.DRACO_URLS" in resp.text assert '"agent": "https://agent-dev.dracodev.net"' in resp.text - assert '"tiktak": "https://tiktak.dracodev.net"' in resp.text + assert '"tiktak": "https://tiktak-dev.dracodev.net"' in resp.text + assert '"gitea": "https://git.dracodev.net"' in resp.text