Le serveur central
openplate-core relaie ton journal entre tes appareils. Il stocke une adresse e-mail et une copie chiffrée de ton journal. Il conserve aussi ton code de récupération, scellé sous un secret qui lui est propre, afin qu'un mot de passe oublié te permette de récupérer ton journal. Ce même code permet à la personne qui administre l'instance d'y ouvrir un journal.
Ce qu'il peut et ne peut pas lire
openplate-core transfère un journal d'un appareil à l'autre. C'est le seul service d'openplate qui gère des comptes. Il s'agit d'un composant déployable distinct, avec sa propre image, sa base de données et son secret, et le navigateur communique directement avec lui. Le serveur de l'application ne relaie rien pour son compte et ne dessert aucune route de synchronisation.
Le journal est chiffré avant de quitter l'appareil. Le client sérialise la base locale, la compresse avec gzip, la chiffre en AES-256-GCM à l'aide d'une clé de données aléatoire, et envoie le résultat sous la forme d'un blob opaque unique. La clé de données est enveloppée sous une clé dérivée de ta phrase secrète : la phrase secrète est étirée avec Argon2id puis divisée par HKDF en branches indépendantes. Deux d'entre elles restent sur l'appareil pour déchiffrer ce qui t'appartient, et une troisième est envoyée comme identifiant de connexion. Ce sont des branches sœurs, sans lien de parenté direct, si bien que détenir l'identifiant ne révèle rien sur la clé.
L'opérateur détient une clé de récupération. Lors de l'inscription, l'application génère un code de récupération et y enveloppe la clé de données. Elle transmet le code à openplate-core, qui le scelle sous son propre secret. C'est ce mécanisme qui permet à une réinitialisation de mot de passe par courriel de restituer ton journal plutôt qu'un compte vide. Cela signifie également que la personne qui administre l'instance peut restaurer, et en principe lire, un journal stocké dessus. Sur une instance que tu héberges toi-même, cet administrateur, c'est toi. sync.md détaille l'arbitrage dans son ensemble.
Ce que le serveur voit en dehors du texte chiffré est décrit sans fard dans PROTOCOL.md §9 : une adresse courriel, la taille du blob, la fréquence et les heures d'écriture, les numéros de version, et les paramètres KDF.
La console d'étude facultative gère ses propres comptes distincts sur le même serveur. Synchronisation indique quand elle est active, et ADR-0008 explique pourquoi elle réside dans l'application.
Ce qu'il sait
Être transparent sur les métadonnées, car « chiffré de bout en bout » est souvent interprété comme « le serveur ne sait rien » :
- Taille du blob, et par conséquent une estimation du volume de données que détient le compte. La compression rend ce signal plus flou qu'auparavant, mais ne le masque pas.
- Fréquence et calendrier des écritures : quand un appareil se synchronise, et à quelle fréquence.
- Numéros de version :
blobVersion,envelopeVersionet le nombre de versions conservées. - Paramètres et sel de la KDF pour l'enregistrement de la phrase de passe. Ce ne sont pas des secrets, ils existent pour être transmis à un nouvel appareil avant la connexion.
- Si un compte a terminé sa configuration (possède des enregistrements de clés) et s'il s'est déjà synchronisé (possède un blob).
- Le compte lui-même : une adresse e-mail, un nom d'affichage facultatif, un rôle, un quota quotidien d'IA, une date et heure de suspension, un vérificateur d'authentification (un hachage avec clé d'un hachage avec clé de la phrase de passe, voir §5.8), un second vérificateur construit à l'identique sur la preuve de récupération, et les paramètres KDF du compte. L'adresse désigne une personne réelle, ce qui correspond à une catégorie de données personnelles retirée par la version 0.5.0 et réintroduite délibérément par la 0.6.0 (ADR-0005) : les membres d'une organisation sont identifiés par l'adresse à laquelle leur invitation est arrivée, car c'est l'identifiant dont ils se souviendront encore dans un mois.
- Le CODE DE RÉCUPÉRATION du compte, scellé (
accounts.recovery_code_escrow, §3.1). C'est l'élément de cette liste sur lequel un lecteur doit s'arrêter. Il est chiffré en AES-256-GCM sous une sous-clé deSERVER_SECRET, donc une simple copie de la base de données ne suffit pas à l'ouvrir, mais l'administrateur d'une instance gérée possède les deux. L'administrateur d'une instance gérée peut ouvrir n'importe quel compte présent sur celle-ci. Pas via un point de terminaison, ni via un chemin de code de ce service, mais en lisant cette colonne avec le secret en main et en exécutant la propre HKDF du client. Une instance auto-hébergée a son propre administrateur, donc l'engagement initial s'y applique. Décider de faire confiance ou non à une instance hébergée revient donc à décider de faire confiance à son administrateur. - Invitations en attente : pour chacune, une adresse, un nom optionnel, un rôle et un quota, appartenant à quelqu'un qui n'a AUCUN compte et n'a donné aucun consentement. En créer une est une action de l'opérateur, et
DELETE /v1/admin/invites/:idretire la ligne. Une ligne créée par la porte de requête du §5.8.3 est marquée comme telle, afin qu'un opérateur puisse les compter. Une invitation terminée perd son adresse et son nom dans l'heure : une ligne révoquée ou expirée sur chaque instance, une ligne utilisée sur une instance avecTRIAL_ADDRESS_PEPPER, qui ne conserve que le hachage à clé ci-dessous. Sans le grain de sel, une ligne utilisée conserve son adresse, car la règle de réinvitation de membre du §5.21 la lit. - La période d'essai d'analyse, sur une instance qui en exécute un : les analyses gratuites accordées et utilisées, deux entiers sur la ligne du compte. Pour un compte en essai d'analyse uniquement, une ligne par action d'IA : un identifiant opaque choisi par le client, une heure, un décompte de requêtes et le fait qu'une réponse ait été remise ou non, conservé 24 heures puis supprimé, et jamais consigné. Chaque ligne d'invitation porte un hachage à sens unique à clé de son adresse de courriel (HMAC-SHA256 sous
TRIAL_ADDRESS_PEPPER, un secret que seul l'opérateur détient, sur la clé d'essai du §5.8.3), jamais une seconde copie de l'adresse. - Après la suppression d'un compte, sur une instance qui propose une période d'essai d'analyse : l'adresse et le nom sont supprimés de chaque ligne d'invitation relative à cette boîte aux lettres, et, uniquement lorsque le compte disposait d'une période d'essai, un hachage à clé de l'adresse de courriel est conservé, et rien d'autre : aucun nom, aucun identifiant, et une seule date, l'instant de la suppression, ce qui permet à la ligne de se terminer. C'est ce qui empêche cette même boîte aux lettres d'obtenir une deuxième période d'essai. Sans le secret de l'opérateur, le hachage ne peut être inversé ni comparé à une liste d'adresses. Un nettoyage le supprime
TRIAL_HASH_RETENTION_DAYS(365 par défaut) après cet instant, sur la base juridique de l'art. 6(1)(f) RGPD (un intérêt légitime à empêcher les abus des analyses gratuites, non validé par un juriste, ADR-0010). Une instance qui n'accorde aucune période d'essai d'analyse ne conserve aucun hachage. Sur une instance qui ne propose pas de période d'essai d'analyse, les lignes d'invitation conservent leur adresse après une suppression, pour la règle de réinvitation de membre du §5.21. - Utilisation de l'IA : un entier par compte et par jour UTC, conservé pendant 90 jours puis supprimé (§5.20). Un compteur, jamais un journal : aucun prompt, aucune réponse, aucun modèle, aucun horodatage au-delà du jour. Un opérateur peut lire les compteurs d'un compte sous la forme d'une frise quotidienne (
GET /v1/admin/accounts/:id/activity), qui constitue une métadonnée sur les moments où une personne a utilisé une application de santé, et reste délimitée précisément pour cette raison. - Le pouls communautaire, pour les comptes qui l'ont activé (§5.23, ADR-0007) : les sommes journalières à l'échelle de l'instance pour les repas, les photos, les calories et les grammes de protéines, une ligne par compte contributeur et par jour, ainsi qu'une ligne de présence éphémère indiquant qu'un compte jeûne en ce moment. Les sommes ne peuvent être attribuées à personne ; la ligne de contribution et la ligne de présence le sont, et indiquent seulement « ce compte a contribué aujourd'hui » et « ce compte jeûne ». Les sommes journalières et les lignes de contribution sont gardées 30 jours, la présence expire 30 minutes après le dernier battement de cœur, et les routes ne journalisent aucun identifiant de compte. Une personne qui ne l'a jamais activé n'envoie rien et n'apparaît nulle part.
- Un abonnement push, pour un appareil dont le propriétaire a activé les notifications (§5.24, ADR-0008) : le point de terminaison du service push, les deux clés de chiffrement, une chaîne d'agent utilisateur tronquée, un fuseau horaire IANA, une locale, la minute du jour local où un rattrapage est prévu, le jour local du dernier envoi, le jour local où l'appareil a été vu pour la dernière fois, l'instant où il a demandé à être réveillé, et un décompte de ce qui a été envoyé aujourd'hui. L'ensemble indique approximativement quand cette personne est éveillée, approximativement où elle se trouve dans le monde, et, via
wake_at, quand son jeûne se termine. Ce dernier élément concorde avec la ligne de présence du pouls, qui indique que ce même jeûne est en cours ; ADR-0008 explicite cette corrélation au lieu de la laisser deviner. Ce qui n'est PAS stocké, c'est le moindre mot du texte d'une notification : chaque notification push transporte un type. La ligne disparaît quand l'appareil se désabonne, quand le service push le rejette, ou avec le compte. - Un consentement relatif aux données de santé, sur une instance qui en demande un (§5.15.1) : la version du texte acceptée par la personne et l'instant où ce service l'a enregistrée, deux colonnes sur la ligne du compte. Cela indique que la personne utilise une application de santé et a accepté que l'opérateur traite ces données, ce que l'opérateur doit pouvoir prouver. C'est visible par un opérateur (§5.20), aucune route ne l'efface, et cela disparaît avec la ligne du compte lors de la suppression.
- Date et heure de la dernière action d'une personne :
accounts.last_seen_at, écrit lors d'une connexion et d'une complétion relayée, et délibérément pas lors d'un rafraîchissement de jeton ou d'un sondage de synchronisation, ce qui signifie « quelqu'un a agi » plutôt que « un client tournait ». Il est visible pour un opérateur (§5.20) et disparaît avec la ligne du compte lors de la suppression. - Déclarations légales (
POST /v1/legal/declarations, une résiliation ou une rétractation, sur chaque instance) : le nom, l'adresse, la référence du contrat, le motif et les dates saisis par la personne, l'heure de réception, et le compte correspondant, le cas échéant. Elle est conservé jusqu'à la fin de la troisième année civile suivant son arrivée, décomptée selon l'heure Europe/Berlin, puis supprimée par le nettoyage horaire : une déclaration reçue le 2026-09-21 est supprimée à partir du 2030-01-01 00:00 à Berlin. Supprimer le compte ne le supprime pas plus tôt ; la ligne perd son identifiant de compte et reste en place, car elle constitue la trace de ce que la personne a déclaré. L'envoi d'accusés de réception est plafonné à trois par adresse normalisée, àLEGAL_DECLARATION_RECEIPTS_PER_NETWORK_PER_DAY(10 par défaut) par réseau émetteur, et àLEGAL_DECLARATION_RECEIPTS_PER_DAY(200 par défaut) par instance. Chaque plafond s'applique sur toute période glissante de 24 heures. Les totaux sont calculés à partir de ces lignes, sauf le décompte par réseau, qui reste en mémoire. Une déclaration au-delà d'un plafond est malgré tout enregistrée, transmise, et envoyée à l'administrateur, et le202reste identique. L'accusé de réception ne répète jamais le nom, la référence du contrat, ni le motif ; seule la copie destinée à l'administrateur les contient. - Métadonnées de session : le nombre de sessions actives existantes, la date de création de chacune et la date de dernière rotation ou révocation des jetons. Les valeurs des jetons elles-mêmes ne sont stockées que sous forme de condensats.
- Le graphe d'étude, sur un déploiement où
SYNC_RESEARCHest défini (§5.18) : quel compte contribue à quelle étude, quand, à quelle fréquence et quelle est la taille de chaque contribution. Une arête indique ici « les données de santé de cette personne se trouvent dans l'étude Y », ce qui constitue des données personnelles indirectement liées à la santé, de la même catégorie que l'arête de soins ci-dessous. C'est inévitable, et le retrait en est la preuve : supprimer la ligne d'un contributeur nécessite de la localiser, la suppression d'un compte doit se propager en cascade dessus, et le compare-and-swap comme la lutte contre les abus s'appuient sur le compte. Un schéma qui masquerait ces données au serveur casserait l'un de ces éléments et l'analyse du trafic lèverait le masque de toute façon, c'est pourquoi cela est exposé plutôt qu'évité à moitié. Le chercheur ne reçoit jamais la correspondance (§5.18 ne transporte aucun identifiant de compte), le retrait supprime définitivement l'arête et ne laisse qu'un pseudonyme, et un déploiement sans ce drapeau ne possède aucune table pour stocker un graphe d'étude. - Le graphe de partage, sur un déploiement où
SYNC_SHARINGest défini (§5.16) : quel compte a accordé un accès en lecture à quel autre compte, quand l'accès a été accordé et quand le bénéficiaire l'exerce. Il s'agit d'un graphe de relations, et d'un réel élargissement de ce que ce service sait, et dans le cas pour lequel la fonctionnalité a été conçue (un patient et son diététicien), une arête de ce graphe constitue en soi une donnée personnelle indirectement liée à la santé, car elle indique qu'une personne fait l'objet d'un suivi médical. C'est le strict minimum pour autoriser la lecture ; les deux parties y consentent, puisque la personne qui accorde l'accès crée la ligne et que le bénéficiaire peut supprimer son côté ; enfin, l'arête est définitivement supprimée lors de la révocation et disparaît en cascade lors de la suppression de l'un des deux comptes. Un déploiement qui ne définit pasSYNC_SHARINGne stocke aucun graphe de ce type et ne dispose d'aucune table pour en accueillir un.
- Estimations signalées, sur un déploiement avec
SYNC_FEEDBACKactivé (§5.25, ADR-0006) : les chiffres de chaque entrée qu'une personne a choisi de signaler, la photo d'assiette quand l'appareil en avait encore une, l'identifiant du compte, l'enregistrement du consentement et l'heure d'arrivée, tous lisibles. Ils sont conservés pendantinstance.feedback.retentionDays, puis supprimés par un nettoyage, et ils disparaissent avec le compte. Un opérateur peut les lire via le §5.20, et chaque lecture d'une photographie est journalisée. Un déploiement sans cette option n'a aucun signalement ni aucune photographie à conserver.
Impossible à déduire des métadonnées ci-dessus : ce qui a été mangé, quand, en quelle quantité, ou quoi que ce soit d'autre dans la charge utile. Deux entrées ci-dessus le révèlent en revanche. Le code de récupération scellé ouvre tout le journal à quiconque détient aussi SERVER_SECRET, et une estimation signalée montre l'unique entrée qu'elle contient.
Instances gérées
Une instance peut définir INSTANCE_MODE=managed (voir configuration.md). Cela déclare une chose : une organisation fait tourner cette instance, invite ses membres par e-mail et attribue à chacun un quota d'IA quotidien. openplate-core est ce qui porte cela, le compte qu'il détient déjà pour la synchronisation détient aussi le quota, il n'y a donc pas de seconde étape de connexion ni de second identifiant.
Pour le navigateur, rien ne change : un compte connecté disposant d'un quota effectue ses analyses via le proxy IA exposé par openplate-core, le même service auquel le client s'adresse déjà pour la synchronisation. Pour le système situé derrière, openplate-core agit comme un client : il pointe soit vers un fournisseur cloud, soit vers ton propre conteneur openplate-inference. L'inférence correspond à la couche de calcul, openplate-core forme la couche de gestion des accès sur une instance gérée, et les deux s'associent : le serveur central n'embarque aucun modèle et ne traite aucune analyse lui-même.
Il se trouve sur le chemin des photos, ce qui représente son coût réel, et la parade relève directement du code plutôt que d'un réglage : le type de champ du logger n'accepte que des types primitifs, de sorte qu'aucun corps de requête ne peut atterrir dans une ligne de log, et les messages d'erreur amont sont nettoyés avant d'être consignés ou renvoyés. Les membres d'une organisation mutualisent les dépenses, pas leurs données ; une photo d'assiette qui arrive sur le proxy est lue une seule fois et n'est pas conservée.
Le quota compte le nombre de requêtes, pas un montant financier. L'administrateur peut également plafonner l'ensemble de l'instance par jour (AI_INSTANCE_DAILY_LIMIT), et un plafond de dépenses sur la clé du fournisseur reste indispensable.
L'administration de l'instance s'effectue depuis /admin dans l'application : comptes et quotas associés, invitations, activité, estimations signalées, et choix des valeurs de référence affichées sur l'écran Nutriments.
Historique
D'août à septembre 2026, il s'agissait d'un service distinct, openplate-gateway : un petit proxy compatible avec OpenAI qui conservait une clé amont et attribuait à chaque membre un jeton opk_… avec son propre quota quotidien. La version M192 (septembre 2026) l'a fusionné dans openplate-core : un seul compte porte désormais à la fois le journal et le quota, il n'y a donc plus de second service, plus de second lien d'invitation et aucun second identifiant à distribuer.
Documentation: protocole
Versions: notes de version