Files
ObsiGate/docs/features/tiktak-integration-197-200.md
T
bruno f783394f09
CI / lint (push) Successful in 3m17s
CI / security (push) Failing after 1m51s
CI / test (push) Successful in 5m14s
CI / build (push) Successful in 1m50s
CI / e2e (push) Successful in 18m18s
feat: intégration TikTak #197-200 + scoped tokens, webhooks filtrés, listes mtime/size ; fix e2e-server BUG-112
2026-10-14 11:42:53 -04:00

7.3 KiB

Intégration TikTak ↔ ObsiGate — socle côté ObsiGate (#197 → #200)

Statut : ✅ livré — IDs stables : #197, #198, #199, #200 Issues Gitea : Projets/ObsiGate #4, #3, #2, #1 (même ordre) Référence d'analyse : Projets/ticktick-clone, docs/SYNCHRO-OBSIGATE.md v1.1 (décisions §8 : vault dédié « TikTak », transport API REST, TikTak maître en conflit)

Contexte

TikTak projette ses tâches/notes en fichiers Markdown dans un vault ObsiGate dédié via l'API REST fichiers. Côté ObsiGate, quatre manques bloquaient une synchronisation propre : réconciliation sans relire chaque fichier (#197), webhooks filtrables (#198), clé API à moindre privilège (#199), procédure de provisionnement (#200).

#197 — Listes de fichiers : mtime + size (Gitea #4)

Constat. GET /api/vault/{vault}/files et l'outil MCP list_all_files exposaient déjà size/modified, mais pas GET /api/vault/{vault}/paths (seulement vault/path/name/type), ni GET /api/browse/{vault}, ni l'outil list_directory.

Livré.

  • GET /api/vault/{vault}/paths : chaque entrée fichier expose size, mtime (epoch) et mtime_iso (issus de l'index — source unique, même contrat que list_all_files) ; les répertoires portent None.
  • GET /api/browse/{vault} : modified/modified_iso sur les fichiers.
  • Outil MCP list_directory : size/mtime/mtime_iso sur les fichiers.
  • Cohérence read-after-write : PUT /api/file/{vault}/save rafraîchit l'index de façon synchrone (update_single_file), au lieu de dépendre du watcher.
  • Bug latent corrigé au passage : update_single_file/remove_single_file exigeaient un chemin absolu alors que toutes les routes leur passent un chemin relatif au vault — le rafraîchissement post-écriture était un no-op silencieux (masqué en prod par le watcher). Les deux fonctions acceptent désormais les deux formes.

Réconciliation type (un seul appel) : snapshot {path: (mtime, size)}, puis re-liste et comparaison — démontré par tests/test_sync_lists.py::TestReconciliationScenario.

#198 — Webhooks : vault + filtre vault / path_prefix (Gitea #3)

Constat. Les charges utiles contenaient déjà vault, mais sans filtre un consommateur recevait tous les vaults. De plus file_modified (événement souscrit mais jamais émis) manquait pour tirer les retours de synchro.

Livré.

  • Filtres optionnels par webhook : vault (égalité exacte) et path_prefix (égalité ou sous-arbre — Maison matche Maison/a.md, pas Maison2/a.md ; sur renommage/déplacement, old_path ou new_path suffit pour être prévenu). Sans filtre : reçoit tout, comme avant (compatibilité).
  • POST /api/webhooks accepte vault/path_prefix ; PATCH les modifie ("" réinitialise) ; GET les expose (WebhookModel.vault/path_prefix).
  • PUT /api/file/{vault}/save émet désormais file_modified ({"vault", "path"}) — la sauvegarde tableur/IA reste hors périmètre (documenté).
  • UI : champs filtre à la création + badge dans la liste ; aide intégrée FR/EN mise à jour (file_modified émis, filtrage documenté).
  • Filtrage pur et testable : backend/webhooks.py::webhook_matches ; tests/test_webhook_filters.py (matching, persistance, dispatch simulé : le webhook filtré sur « TikTak » ne reçoit que ses événements, avec vault: "TikTak" dans la charge).

#199 — Clés API à portée limitée (Gitea #2)

Constat. Les clés longue durée portaient toutes les permissions du compte.

Livré.

  • Création : POST /api/auth/tokens accepte vaults (sous-ensemble des vaults du créateur, défaut = tous — 400 si inconnu, 403 si hors d'accès) et scope ("full" défaut | "files").
  • Restriction vault : le JWT embarque les vaults autorisés → check_vault_access (REST) et require_vault_access (MCP/outils) refusent le reste en 403.
  • Périmètre files : fichiers (CRUD, listes, recherche, backups lecture/restauration), GET /api/vaults, /api/search*, /mcp. Tout le reste (admin, config, IA, webhooks, partages, gestion des clés elle-même — anti-escalade) → 403 via TokenScopeMiddleware (backend/auth/scope.py, stateless : claim lu sans vérification pour le refus, signature toujours vérifiée en aval).
  • MCP : call_tool + propose_* refusent les outils hors FILES_SCOPE_TOOLS (token_scope_denied), list_tools filtré pour ces tokens, ressources vault:// couvertes.
  • UI : sélecteur de portée + champ vaults à la création, badge de portée et vaults dans la liste (i18n FR/EN).
  • Compatibilité : clés existantes (sans scope) = full, comportement inchangé.
  • Tests : tests/test_scoped_tokens.py (13 cas : création/validation, lecture/ écriture OK, 403 autre vault, 403 hors périmètre, non-régression full, outils).

#200 — Provisionnement du vault « TikTak » (Gitea #1, exploitation)

Opération unique côté hébergement (aucun code requis — endpoints déjà testés) :

# 1. Dossier sur l'hôte
mkdir -p /NFS/OBSIDIAN_DOC/TikTak

# 2. Montage conteneur (docker-compose.yml, puis recreate)
# volumes:
#   - /NFS/OBSIDIAN_DOC/TikTak:/vaults/TikTak

# 3. Déclarer le vault SANS redémarrage (admin, persisté via data/vaults.json)
curl -X POST $BASE/api/vaults/add -H "Authorization: Bearer $ADMIN" \
  -H 'Content-Type: application/json' -d '{"name":"TikTak","path":"/vaults/TikTak"}'

# 4. Octroyer le vault au compte de synchro (ACL par vault, UI admin ou API)

# 5. Clé à portée limitée pour TikTak (cf. #199)
curl -X POST $BASE/api/auth/tokens -H "Authorization: Bearer $ADMIN" \
  -H 'Content-Type: application/json' \
  -d '{"name":"TikTak sync","expiry":"never","vaults":["TikTak"],"scope":"files"}'

# 6. Webhook retour filtré (optionnel, phase 3 TikTak)
curl -X POST $BASE/api/webhooks -H "Authorization: Bearer $ADMIN" \
  -H 'Content-Type: application/json' \
  -d '{"name":"TikTak retour","url":"https://tiktak…/hooks/obsigate","events":["file_created","file_modified","file_deleted","file_renamed"],"vault":"TikTak"}'

Acceptation : le vault « TikTak » est visible ; un .md créé via POST /api/file/TikTak (clé limitée, pas admin) est lu, cherché et édité sans erreur :

KEY=<clé de l'étape 5>
curl -X POST $BASE/api/file/TikTak -H "Authorization: Bearer $KEY" \
  -H 'Content-Type: application/json' -d '{"path":"Inbox/sonde.md","content":"# sonde\n"}'
curl -s "$BASE/api/file/TikTak/raw?path=Inbox/sonde.md" -H "Authorization: Bearer $KEY"
curl -s "$BASE/api/search?q=sonde&vault=TikTak" -H "Authorization: Bearer $KEY"

Fichiers touchés (rappel)

# Backend Frontend Tests
197 services/search.py, services/vaults.py, schemas.py, indexer.py, routers/files_write.py, tools/service.py — tests/test_sync_lists.py
198 webhooks.py, schemas.py, routers/webhooks.py, routers/files_write.py js/config.js, index.html, locales/{fr,en}.json tests/test_webhook_filters.py
199 auth/jwt_handler.py, auth/middleware.py, auth/scope.py (nouveau), auth/router.py, main.py, tools/context.py, tools/registry.py, tools/api.py, mcp/server.py, openapi_docs.py js/config.js, index.html, locales/{fr,en}.json tests/test_scoped_tokens.py
200 — (runbook) — — (recette manuelle ci-dessus)