Notion
Deux directions, dont une seule est une boucle.
Connectez Notion et tous ceux qui n'ouvrent pas Kanso lisent les mêmes données là où ils travaillent déjà. C'est un miroir, pas une seconde source de vérité, et cette page parle surtout de cette différence.
La décision dont tout découle
Notion ne peut pas se tenir entre deux personnes qui éditent le même tableau. Un appel prend environ 300 à 800 ms, l'intégration est plafonnée autour de trois requêtes par seconde, et il n'y a pas de push sur lequel compter. Tout ce qui passe par là hérite de ces chiffres.
Postgres est la source de vérité opérationnelle. Notion est un miroir asynchrone. Kanso écrit dans Postgres, vous répond, et pousse vers Notion ensuite.
Kanso reste donc pleinement utilisable pendant que Notion est injoignable, et se réconcilie après. Rien de ce que vous faites dans l'application n'attend un appel réseau vers le service de quelqu'un d'autre.
Sortant, en continu
Chaque écriture met un travail de synchronisation en file dans la même transaction que le changement métier : un crash ne peut pas en perdre un. Un worker les dépile avec FOR UPDATE SKIP LOCKED, se limite par un token bucket, réessaie en backoff exponentiel, et écrit dans quatre bases :
- Kanso · Teams
- Kanso · Projects
- Kanso · Tickets
- Kanso · Docs
Ces quatre-là sont tout le miroir, et ce sont des bases Notion ordinaires. Ceux qui ont seulement besoin de suivre les filtrent, les groupent et les représentent comme ils le font déjà, et n'ouvrent jamais Kanso.
Les travaux portent une priorité de dépendance — équipe, puis projet, puis ticket, puis document — parce qu'une relation exige que sa page cible existe d'abord. Une poussée dont la cible n'a pas encore de page est différée de quelques secondes plutôt que mise en échec.
Entrant, par sondage
Notion n'a pas de push fiable, donc Kanso sonde de façon incrémentale sur last_edited_time, avec un curseur persisté par source de données et une suppression d'écho pour que ses propres écritures ne reviennent pas comme des changements.
Le poller n'applique que les champs scalaires — titre, statut, priorité, dates, drapeau d'archivage. Il n'applique pas les relations : Notion remplace un tableau de relations en entier, et l'accepter en retour transformerait une édition concurrente en perte de données silencieuse. Il n'applique pas non plus les personnes : une propriété people ne sait pas représenter un utilisateur Kanso sans compte Notion, donc la relire ferait disparaître des assignés sans bruit. Relations et assignations restent la propriété de Kanso.
Une page créée à la main dans Notion n'est pas adoptée. Un ticket a besoin d'une équipe et d'un numéro propre à cette équipe, et une page créée dans le miroir ne peut fournir ni l'un ni l'autre ; le poller la journalise et passe, plutôt que d'inventer des lignes.
Kanso gagne, et ce que cela coûte
Si la ligne Postgres a bougé depuis la dernière poussée réussie, une modification faite dans Notion est écartée et une poussée corrective est mise en file. Notion reconverge seul — ce qui veut dire que celui qui édite dans Notion verra sa modification revenir en arrière.
C'est la conception, pas un défaut — mais il faut le dire à voix haute à ceux qui lisent le miroir, sinon ils perdent confiance dans l'outil. Posez un bandeau sur la page parente disant que ces bases sont maintenues par Kanso. Faire de l'édition côté Notion un citoyen de première classe demande une fusion champ par champ et une vraie interface de conflit : c'est une conversation de v2, pas un réglage.
L'import, qui a lieu une fois
Vous arrivez avec six mois de tickets déjà dans Notion, et l'import les adopte : il liste les bases qu'il voit, vous dites quelle équipe chacune devient, et il y copie les pages. Il lit les colonnes propres de cet espace et ses propres mots : une base dont la colonne de statut s'appelle État et dont les options disent En cours arrive comme du travail déjà commencé, et non comme un tas de Todo.
Deux règles empêchent cette exception de refluer dans le miroir. Un ticket importé reçoit sa propre page dans Kanso · Tickets, et la page source n'est jamais adoptée ni écrite. L'adopter mettrait « Kanso gagne » aux commandes d'un espace qu'on vient de vous confier, ce qui est exactement la façon dont un import efface ce qu'il importe.
Une page que l'import ne peut pas prendre — sans titre, ou déjà archivée — est signalée avec son identifiant et sa raison, plutôt que classée comme un Untitled de plus. La règle « une seule fois » est tenue par la base de données et non par le fait de penser à vérifier, ce qui rend l'import sûr à presser deux fois.
La connexion
Depuis l'assistant d'installation, ou plus tard depuis réglages → installation. Une étape doit se passer dans Notion, parce qu'aucun fournisseur n'émet de client OAuth pour un hôte dont il n'a jamais entendu parler : créez une intégration publique et enregistrez l'URI de redirection que Kanso affiche à côté du champ.
Collez ensuite ce que cette page vous donne — son URL d'autorisation, ou le client id et le secret ensemble — dans le champ client id, et les deux sont remplis par ce seul collage. Tout le reste est un bouton. Connect Notion ouvre l'écran de consentement de Notion, qui est aussi l'endroit où vous choisissez les pages que Kanso peut voir : rien n'a à être partagé à la main depuis un menu •••. Create the databases construit alors les quatre et met en file tout ce qui est déjà dans Postgres.
Posez NOTION_CLIENT_ID et NOTION_CLIENT_SECRET dans l'environnement et le collage disparaît pour de bon : chaque instance montée ensuite n'affiche plus que le bouton. Ils épinglent la porte, pas la connexion — rien n'est mis en miroir tant que personne n'a consenti. Tout ce qui est posé dans l'environnement l'emporte sur l'assistant et s'affiche en lecture seule, parce qu'une configuration d'exploitant et une configuration dans l'application qui se contredisent en silence valent moins que l'une ou l'autre seule.
Coller un jeton d'intégration interne fonctionne toujours, replié derrière Paste an integration token instead — pour une instance qui en a déjà un, et pour celle dont le navigateur ne peut pas atteindre un écran de consentement.
Les secrets saisis dans l'assistant sont chiffrés en AES-GCM avant de toucher la base, avec une clé venue de KANSO_SECRET_KEY ou générée dans le volume de données. Sauvegardez cette clé. Sans elle, les secrets stockés doivent être ressaisis.
Là où la correspondance perd
Trois endroits où le modèle de Notion ne sait pas tenir celui de Kanso, énoncés ici plutôt que découverts :
- people
- Une propriété
peoplen'accepte que des membres de l'espace Notion. Kanso l'écrit quand il connaît l'identifiant Notion de la personne, et écrit toujours en parallèle un texte richeAssignees (Kanso)avec le nom de chacun, pour que le miroir reste lisible. Cette colonne de texte est ignorée au retour. - relations
- Écrire une relation remplace tout le tableau ; il n'y a pas de fusion. Chaque poussée écrit donc l'état complet de l'entité depuis Postgres, ce qui est cohérent avec « Kanso gagne » et la raison pour laquelle aucune poussée partielle n'est jamais tentée.
- page parente
- La page sous laquelle les quatre bases sont créées se choisit dans une liste plutôt qu'en nommant un identifiant. La liste exclut toute page dont le parent est une base, parce qu'une recherche filtrée sur les pages renvoie les lignes de base — et chaque page que le miroir écrit en est une.
L'exploiter
Deux points d'entrée, tous deux derrière une session propriétaire ou administrateur :
GET /api/admin/sync
POST /api/admin/notion/reconcile
Le premier rapporte la profondeur de file, les échecs et les curseurs de sondage. Le second réécrit chaque page depuis Postgres, et c'est ce vers quoi se tourner après une panne ou une modification en masse faite au mauvais endroit. Une réconciliation complète met au plus 500 tickets en file par appel, et le dit dans le journal.
Sans aucun jeton, chaque écriture met quand même un travail de synchronisation en file et un client neutre le termine. La file et le mécanisme de reprise tournent, et rien ne quitte la machine — c'est aussi ce qui rend Notion véritablement optionnel plutôt que nominalement.