ACC-5.1 (#24) — app/routers/mcp_server.py : catalogue §7.2 complété
(list_projects, get_project, read_page tronqué proprement,
list_project_tasks), search_pages en plein texte, liens profonds +
kind sur chaque résultat, scopes du jeton appliqués (écriture
refusée -32002 sans scope write) et journalisation §7.4 de chaque
tools/call dans api_audit_log (clés d'arguments seulement).
ACC-2.2 (#20) — pont MCP Flowdeck → ObsiGate, côté client :
- mcp_client : serveurs à session (capture/réémission de
Mcp-Session-Id, handshake avant tools/call), décodage SSE
multi-messages par id de réponse, classification des outils à la
découverte (readOnlyHint sinon heuristique ; lecture par défaut).
- preset connecteur « obsigate » (URL dérivée dev/prod), kinds MCP
factorisés (connectors.MCP_KINDS), option dans le panneau agent.
- garde-fou n° 2 : permission_manager.is_write_tool couvre les
outils MCP écrivants → portes politique d'approbation
(agent_engine) et rôles (assert_can) ; McpTool bloque tout
apply_* distant (patron §7.5).
Tests : test_fusion_mcp_server 18, nouveau test_fusion_pont_obsigate
7 (faux serveur ObsiGate à session stricte via le vrai _rpc).
Preuve réelle T0-T5 : attend la clé ObsiGate dédiée (1 vault) et
Q-A/Q-B côté Bruno.
Le mode console du panneau restait une page morte sur flowdeck-dev :
CSP frame-src/connect-src et DRACO_PANNEAU_CONFIG.agent_url pointaient
l'agent prod (pas encore installée). Deux trous de la 7.82.3 :
agent_url() sans aucune dérivation, et aucune résolution lisant
l'hôte de la requête.
Pour tout ce qui est rendu à une requête (barre/DRACO_URLS, iframe
du panneau, CSP, boutons et redirections SSO) : explicite >
hôte public de la requête (motif fermé
^(og|flowdeck|tiktak|kanbante|agent)(SUFFIXE).dracodev.net$,
suffixe appliqué aux sœurs et à l'agent) > dérivation par les
adresses de l'agent connues en config (élargie : agent_url() se
dérive de l'URL d'événements explicite) > défaut officiel.
L'hôte est posé par le middleware CSP dans un ContextVar (patron
du nonce CSP) : iframe et CSP de la même réponse consomment la
même origine résolue. Contextes sans requête inchangés sinon.
Tests : +12 dans test_draco_urls_devprod.py (25/25) — Host dev
sans aucun env → tout en -dev ; Host prod → officiels ; Host LAN
→ repli config/défauts ; explicite gagne sur l'hôte ; motif fermé.
Suite complète : 4 failed / 26 errors identiques sur arbre propre
(familles préexistantes MCP/realtime/my_tasks/gallery/sync).
- mock_http (test_agent_web_tools) : og_fetcher, importé paresseusement
par url_fetch pendant la fenêtre patchée, figeait la factory mockée
'build' comme shared_client ; son handler répondait ensuite aux
fetch_og_metadata des tests suivants du même worker xdist (flake
test_v55 : « Titre de la page »). La restauration du finally couvre
désormais og_fetcher, comme elle couvrait déjà url_fetch.
- test_editor_front_end_wired : depuis la refonte templates (fc85481),
le câblage page-templates vit dans page_editor_scripts.js (le picker
de base.html passe par le registre unifié /api/templates) ; le
marqueur est vérifié dans le fichier qui le porte, à force égale.
Les pages-supports créées par TemplateService._create_holder (une par
template, search_excluded=1, « masquées, jamais listées ») n'étaient
exclues d'aucune liste : semées à l'init avec updated_at = now (99 sur
une base fraîche), elles occupaient toutes les places des listes
triées par récence (LIMIT 50/20/8) devant les vraies pages — cause
racine de 7 échecs CI (library recents/private/workspace, sidebar
recents, projets builtin, mentions agent), dont l'instabilité du test
provenance (égalité de seconde avec le seed).
Filtre COALESCE(search_excluded, 0) = 0 ajouté : onglets bibliothèque
(meetings, recents, favorites, shared, published, private, workspace),
sidebar Recents, projets builtin, scans de pages des mentions agent.
Lint (run 2304, job 7058, ruff check app tests) : E402+I001 import mcp_server remonté en tête de app/main.py ; B904 x8 dans api_v2/templates_io.py (raise ... from None, from exc pour ValueError) ; UP017 datetime.UTC dans agent_evenements.py ; F841 x3 dans fd_templates.py (void/uid morts, import inutilisé retirés en chaîne) ; F401+F841 dans tests/test_fd_templates.py (import json inutilisé, n_presets en appel nu).
Version : le test test_pwa_offline cherche le littéral version="7.82.0" dans app/main.py — patron historique des bumps (scripts/bump_version.py le cherche aussi), rompu en 7.80.2 quand l'argument FastAPI est passé à _lire_version(). Littéral reposé en commentaire de synchronisation explicite, version runtime inchangée (lecture dynamique VERSION).
list_workspaces/get_workspace filtrés par user_workspace_ids ;
collections filtrées par workspace effectif ; hors périmètre =
introuvable, lectures et écritures. Test deux comptes.
- detach_page_references : nettoyage des références NO ACTION avant
toute suppression définitive (unitaire, Empty Trash, purge 30 j)
- MCP : list_workspaces / get_workspace ; descriptions collections
clarifiées (bases de données, pas des workspaces)
- 96 tests verts sur le périmètre (MCP, trash, cohérence)
Le middleware CSRF rejetait tout POST /mcp en 403 avant la route ;
les tests passaient car leur client porte session + CSRF. Test de
régression : client anonyme Bearer seul (200 avec jeton, 401 sans).
- create_page: collection_id (int, requis), title (string, requis), icon (optionnel), properties (objet optionnel -> property_values_json)
- update_page: id (int, requis), title (optionnel), properties (objet optionnel, remplace property_values_json)
- create_collection: name (requis), description (optionnel), icon (optionnel) + vue par défaut
- Aucune suppression; erreurs métier en {"erreur": "..."}; GET /mcp read_only=false et liste mise à jour
- Tests étendus: création+relecture, mise à jour, create_collection, erreurs, 401 sans jeton sur écriture
- "Get started with" : suppression du bouton "⋯" et de son menu mort
(Table/Board/List/Timeline/Calendar/Gallery/Import — aucun handler).
- Pilule Form : createForm() plantait sur page vide ("Cannot read
properties of undefined (reading 'push')") car this.inlineDBs n'était
jamais initialisé dans editorState() (seulement via loadInlineDBs si un
bloc inline_dbs existait déjà). Corrigé : inlineDBs:[] initialisé.
Le nom du form est demandé via une modale Alpine personnalisée
(flowdeck-modal*) au lieu du prompt() natif.
- Sidebar tree après drag&drop : refreshSidebarTree() préservait le lien
"Manage Workspaces" via ul.querySelector('li:last-child') — non scopé,
il attrapait le dernier fichier d'une sous-liste de dossier imbriquée
(premier li:last-child du document) et le ré-insérait → doublon du
dernier fichier. Corrigé : ':scope > li:last-child' (enfant direct).