Second Brain documentation

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)

← Toutes les décisions

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/then sur kind) : webhook exige toujours secret, id et fields et refuse list, every, capture, after ; google-tasks exige list et refuse les clés du webhook. Le webhook est inchangé.
  • ConnectorDefinition gagne list, every, capture, after (facultatifs). source vaut 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 ; every est borné ; une valeur de after inconnue est refusée par le méta-schéma. Un fichier invalide est ignoré et signalé dans GET /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ée https://www.googleapis.com/auth/tasks seulement. Les valeurs vivent dans l’environnement, jamais dans config/. 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 de docs/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.

  1. 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).
  2. 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 par CaptureService.create (règles de capture, dates dites à voix haute, tags #), entre dans l’Inbox, reçoit son identité externe (id, lien webViewLink s’il existe, ETag, syncedAt) et un événement sync.created. Les notes Google deviennent le corps du nœud. Un verrou consultatif par (source, id) empêche deux créations simultanées.
  3. Puis after : complete marque la tâche terminée dans Google (PATCH status: completed), delete la 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) et after est 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émente created, une tâche déjà connue unchanged, une erreur errors et renseigne last_error. Une relève réussie sans nouveauté met à jour la dernière relève et last_result = polled sans 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 le Retry-After de 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, source le 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.