ADR 0095 – Connecteur Google Tasks en relève
Accepté (2026-10-17, dans le cadre de l'ADR 0086 §« Connecteur Google Tasks entrant » validé par Aurélien)
ADR 0095 – Connecteur Google Tasks en relève
Statut : Accepté (2026-10-17, dans le cadre de l’ADR 0086 §« Connecteur Google Tasks entrant » validé par Aurélien)
Ligne C3 du fil C du backlog du jalon 4. Le cadre des connecteurs (identité externe, état, événements sync.*, page Santé) est celui de l’ADR 0047, dont le seul genre était le webhook, reçu. Celui-ci est tiré : le backend interroge Google à intervalle régulier. « Ok Google, ajoute … à ma liste » depuis un Nest crée une tâche dans Google Tasks ; le backend la relève.
Décision
Un second genre de connecteur : kind: google-tasks
# config/connectors/google-tasks.yaml
name: google-tasks
label: Google Tasks
kind: google-tasks
list: "Second Brain" # titre de la liste Google relevée
every: 2m # période de relève (30s à 24h, unités s, m, h) ; défaut 2m
capture: true # chaque tâche passe par la capture assistée puis l'Inbox ; false : titre brut dans l'Inbox
after: complete # complete | delete : sort de la tâche Google une fois importée ; défaut complete
- Le méta-schéma distingue les deux genres (
if/thensurkind) :webhookexige toujourssecret,idetfieldset refuselist,every,capture,after;google-tasksexigelistet refuse les clés du webhook. Le webhook est inchangé. ConnectorDefinitiongagnelist,every,capture,after(facultatifs).sourcevaut le nom du connecteur par défaut, comme avant : c’est la valeur du champ source de l’identité externe.- Le chargement contrôle toujours la présence de l’aspect d’identité externe ;
everyest borné ; une valeur deafterinconnue est refusée par le méta-schéma. Un fichier invalide est ignoré et signalé dansGET /api/definitions/status.
Secrets et réglages techniques
- OAuth 2 par jeton de renouvellement :
SB_GOOGLE_CLIENT_ID,SB_GOOGLE_CLIENT_SECRET,SB_GOOGLE_REFRESH_TOKEN, portéehttps://www.googleapis.com/auth/tasksseulement. Les valeurs vivent dans l’environnement, jamais dansconfig/. Le jeton d’accès (une heure) est demandé à la volée, gardé en mémoire et renouvelé avant son échéance ou après un 401. - Sans l’une des trois variables, le connecteur est inactif : aucune relève, aucun appel réseau, et
GET /api/connectors(page Santé) dit lesquelles manquent. Pas d’écran « Connecter Google Tasks » : le jeton s’obtient une fois par la procédure dedocs/deploiement.md, section 9. - Réglages techniques (
application.yml) :app.connectors.google.tasks-url(https://tasks.googleapis.com),token-url(https://oauth2.googleapis.com/token),polling(relève planifiée, vrai par défaut),tick(10 s, grain de l’ordonnanceur),timeout(20 s). Les URL permettent de pointer un faux serveur (WireMock) dans les tests ; aucun test n’appelle Google.
Relève
Un ordonnanceur léger (un fil) se réveille toutes les tick ; pour chaque connecteur google-tasks actif dont la période est écoulée, il relève. Il relit les définitions à chaque réveil : un YAML rechargé change la période sans redémarrage. Une relève à la fois par connecteur.
- Renouveler le jeton d’accès si besoin, trouver la liste par son titre (insensible à la casse), puis lire ses tâches ouvertes (
showCompleted=false, pagination suivie, 500 tâches au plus par relève). - Pour chaque tâche au titre non vide, dans une transaction : si un nœud vivant porte déjà (
source, id Google) dans l’identité externe, rien n’est créé ; sinon la tâche passe parCaptureService.create(règles de capture, dates dites à voix haute, tags#), entre dans l’Inbox, reçoit son identité externe (id, lienwebViewLinks’il existe, ETag,syncedAt) et un événementsync.created. Les notes Google deviennent le corps du nœud. Un verrou consultatif par (source, id) empêche deux créations simultanées. - Puis
after:completemarque la tâche terminée dans Google (PATCH status: completed),deletela supprime (DELETE). C’est la seule donnée qui repart vers Google. Si cet appel échoue, l’erreur est notée ; la tâche reste ouverte côté Google, elle est reconnue comme déjà importée à la relève suivante (aucun doublon) etafterest rejoué.
Limite connue : un nœud importé puis mis à la corbeille alors que after avait échoué serait recréé à la relève suivante (la corbeille n’est pas une cible d’identité, ADR 0047).
Erreurs et état
- État par connecteur dans
connector_state(aucune migration) : une tâche créée incrémentecreated, une tâche déjà connueunchanged, une erreurerrorset renseignelast_error. Une relève réussie sans nouveauté met à jour la dernière relève etlast_result = polledsans toucher aux compteurs, pour que la page Santé montre que le connecteur vit. - Limite connue : la liste est cherchée parmi les 100 premières listes du compte Google (une seule page de
tasklists) ; au-delà, une liste introuvable donne l’erreur « Liste introuvable ». Cas improbable pour un usage personnel. - 401 de l’API Tasks : un renouvellement du jeton puis une seule nouvelle tentative ; refus du renouvellement (
invalid_grant,invalid_client) : erreur claire. 429 : erreur, et la relève suivante attend leRetry-Afterde Google (une heure au plus). 5xx et erreurs réseau : erreur, nouvelle tentative à la période suivante. Les messages d’erreur ne contiennent jamais un secret ni un jeton.
Conséquences
- Le webhook et ses tests ne changent pas ; les clés ajoutées au record sont facultatives.
- Aucun DTO d’API ne change : le client Angular n’est pas régénéré. Pour un connecteur
google-tasks,secret(informatif) liste les trois variables d’environnement attendues,sourcele nom de la source. - Tests : unitaires (méta-schéma, relève sans secret) et d’intégration Testcontainers contre WireMock (dépendance de test
wiremock-standalone). - À vérifier par Aurélien avant d’activer : que la phrase dite au Nest crée bien un élément dans la liste Google Tasks choisie. L’activation réelle attend les trois secrets.
Alternatives écartées
- Notifications push de Google : demandent une URL publique vérifiée et un renouvellement de canal ; la relève à 2 minutes suffit.
- Écran OAuth dans l’application : reporté (ADR 0086).
- Table de dédoublonnage dédiée : l’identité externe de l’ADR 0047 y suffit.