Skip to main content
À côté des capacités JMAP standard, OxiMail sert une famille de capacités d’extension v2 sous urn:oximail:params:jmap:v2:*. Cette page décrit la surface telle que construite : ce que le serveur négocie et sert réellement en v0.30.0. Les extensions sont en cours de rédaction comme Internet-Drafts destinés à l’IETF ; voir drafts IETF pour le programme.

Comment v2 se négocie

v2 n’est pas un commutateur de version : c’est un opt-in par capacité, exactement comme n’importe quelle capacité JMAP :
  • Un client demande une capacité v2 dans using ; les méthodes qu’elle déverrouille deviennent appelables dans cette requête. Rien ne change au comportement v1 pour les clients qui ne demandent jamais.
  • Les noms de méthodes sont des alias à double capacité quand une capacité v1 et une v2 couvrent le même handler : le registre fait correspondre les deux écritures à une seule implémentation, il y a donc exactement un comportement par méthode, quelle que soit la capacité qui l’a déverrouillée.
  • L’ensemble d’exclusion mutuelle est vide en v0.30.0 : associer une capacité v1 à son homologue v2 dans la même requête est permis (voir JMAP Core pour le mécanisme).
  • Comme partout ailleurs, l’objet de session fait foi : l’entrée accountCapabilities de chaque compte pour un URN v2 porte les limites et options annoncées de cette capacité. Détectez les fonctionnalités depuis la session, jamais depuis la version du serveur.

Les objets de capacité v2

Pour chaque capacité, le contrat de fil qui fait foi est ce que la session annonce, plus les comportements des méthodes. Les règles de rigueur (page courrier) s’appliquent aux méthodes v2 à l’identique : arguments et propriétés inconnus sont des erreurs, jamais ignorés en silence.

Ce que les objets de capacité annoncent

Les objets de capacité de la session se décrivent eux-mêmes : les clients construisent leur interface à partir d’eux, au lieu de coder en dur une connaissance du serveur. Les entrées porteuses telles que servies en v0.30.0 :
  • v2:rules porte tout le vocabulaire de l’éditeur de règles : supportedFields (18 champs de condition) et supportedActions (15 types d’action), un client construit son éditeur depuis ces listes, plus les bornes appliquées (maxRulesPerAccount: 1000, maxActionsPerRule: 20, maxConditionDepth: 20, maxRuleNameLength: 256, perRuleExecutionTimeoutMs, maxApplyEmailsPerCall).
  • v2:admin n’apparaît dans accountCapabilities que pour les comptes au rôle administrateur : son absence est ce qui dit au client de ne pas afficher de surface d’administration. Elle annonce supportedPrincipalRoles, les faits de rétention (softDeleteRetentionDays: 30, auditLogRetentionDays: 2557, soit sept ans) et mfaRequiredForSensitiveOperations: true : les changements de rôle et les mutations de tenant par un compte qui a enrôlé une passkey exigent une session authentifiée par passkey.
  • urn:ietf:params:jmap:auditlog (réservée aux administrateurs comme v2:admin) annonce le journal d’audit inviolable : {hashAlgorithm: "sha-256", verificationSupported: true, retentionDays: 2557}. AuditEvent/verify recalcule la chaîne de hachage et signale la première entrée altérée ou manquante.
  • v2:mail annonce les bornes d’envoi appliquées : maxSizeEmail (50 Mio, aussi pré-annoncée par l’extension SMTP SIZE), maxScheduleDelay (un an sur le sendAt d’Email/send) et emailQuerySortOptions, le vocabulaire de tri exact qu’accepte Email/query. L’objet Email v2 porte aussi expiresAt (mise à la corbeille automatique à l’instant choisi), et Email/send accepte requestMdn (accusés de lecture RFC 8098) et tlsRequired (REQUIRETLS RFC 8689, y compris pour les envois programmés).
  • v2:streaming annonce replayWindowSeconds (l’horizon réel de rétention du journal de changements, 90 jours : un Push/catchup depuis n’importe quel état dans la fenêtre rejoue exactement ce qui a été manqué), maxQueryStreamChunk: 1000 et la cadence de vérification des abonnements push.
  • v2:push-patches annonce maxPatchSize (1 Mio), maxBatchEntries: 100, maxPatchesPerSecond: 10, supportedTransports: ["websocket"] et maxConcurrentStreams: 0 (pas de flux de requête poussés par le serveur : le Core/queryStream cadencé par le client est le mode de flux). Un client s’abonne par connexion WebSocket avec EnablePushPatches {types, properties} ; les entrées updated portent alors exactement les propriétés changées, et les cas dégradés retombent sur un simple StateChange (réconciliation par Push/catchup).
  • v2:resource-limits n’annonce plus que des limites qu’un mécanisme applique réellement, depuis les mêmes constantes que les gestionnaires : dont maxTotalRequestsPerMinute, injectée depuis la configuration effective de limitation de débit de l’opérateur (absente quand aucun limiteur ne tourne). maxConcurrentRequests vit sur la capacité core (RFC 8620) et s’applique par compte sur les deux transports JMAP.
Les fils et les conversations ont gagné des surfaces d’organisation de premier rang dans le même cycle : muted par fil (hérité par les messages nouvellement ingérés) et pinned, Thread/split, Thread/merge, et /queryChanges sur chaque type v2 interrogeable.

Le canon de projection

Chaque /get valide son argument properties contre un vocabulaire fermé, propre au type, et avant toute lecture du magasin. Un nom inconnu revient en invalidArguments qui nomme les fautifs ; id est toujours accepté (la RFC 8621 le rend implicite) et * sélectionne toutes les propriétés de premier niveau. C’est une erreur, et non un haussement d’épaules, pour une raison précise. Avec l’ancien motif « sérialiser puis filtrer », une propriété mal orthographiée produisait un succès avec la propriété absente : indiscernable d’une propriété que le serveur aurait choisi de ne pas servir. Un client ne pouvait pas distinguer sa faute de frappe d’une limite du serveur, et personne ne le pouvait davantage en lisant les journaux. Un vocabulaire fermé ne sert que si on peut le lire : le canon est donc publié en forme machine, à côté des types TypeScript générés. DERIVED_<Type> est la partie qu’un client ne peut pas reconstituer seul. Le mayEdit d’un annuaire de tenant est calculé par appelant depuis son rôle de tenant et une expansion de groupes côté serveur ; aucun champ de structure ne le porte, et le type TypeScript seul n’en dit donc rien. Le registre ne liste que de vraies injections hors structure : une propriété qui est un champ de structure réécrit par lecteur, comme myRights, est déjà typée et en est délibérément absente. Le canon publié est un contrat additif seulement, la même règle que la surface --json de la ligne de commande. La capacité v2:projection de la session est le marqueur de conformité qui atteste que le modèle vaut pour tous les types.
Stabilité du fil : au sein de la ligne v0.30, les formes v2 évoluent de façon additive : nouvelles propriétés optionnelles et nouvelles méthodes, pas de renommage ni de changement de sémantique des champs servis. Les durcissements qui transforment un argument jusque-là ignoré en erreur sont étagés (journalisation d’abord, rejet une fois les clients confirmés propres).