evenements-partenaires
Événement (AsyncAPI) — version 0.1.0. Producteur : diapason.
Ce que la plateforme pousse vers le serveur d’un teneur de compte quand un fait métier survient : un versement exécuté, une part d’avoirs devenue disponible, une opération collective déclinée, une transmission de population contrôlée. C’est le pendant des deux contrats REST — ce qui doit être poussé relève des événements, jamais d’un sondage de l’interface, et sans eux un portail interrogerait la situation d’un épargnant en boucle pour découvrir qu’elle n’a pas changé. Diapason ne détient rien et ne décide rien. Ces faits naissent dans les domaines ; la passerelle les republie dans son propre vocabulaire, celui des deux contrats REST. Un événement porte donc des identifiants publiés et des valeurs décidées — jamais une échéance de lot, un identifiant de mesure de conformité ou un paramètre de règle. Le teneur n’est pas nommé non plus : un déploiement ne sert qu’un tenant, et le destinataire sait qui il est. C’est le premier mécanisme du programme qui sorte vers un hôte que nous ne maîtrisons pas.
La spécification
Section intitulée « La spécification »asyncapi: 3.0.0info: title: diapason — les événements servis aux teneurs de compte version: 0.1.0 description: >- **Ce que la plateforme pousse vers le serveur d'un teneur de compte** quand un fait métier survient : un versement exécuté, une part d'avoirs devenue disponible, une opération collective déclinée, une transmission de population contrôlée. C'est le pendant des deux contrats REST — ce qui doit être poussé relève des événements, jamais d'un sondage de l'interface, et sans eux un portail interrogerait la situation d'un épargnant en boucle pour découvrir qu'elle n'a pas changé.
**Diapason ne détient rien et ne décide rien.** Ces faits naissent dans les domaines ; la passerelle les republie dans son propre vocabulaire, celui des deux contrats REST. Un événement porte donc des **identifiants publiés et des valeurs décidées** — jamais une échéance de lot, un identifiant de mesure de conformité ou un paramètre de règle.
**Le teneur n'est pas nommé non plus** : un déploiement ne sert qu'un tenant, et le destinataire sait qui il est. Aucun attribut ne transporte le tenant, comme aucun chemin ne le porte.
C'est le premier mécanisme du programme qui sorte vers un hôte que nous ne maîtrisons pas. x-ruptures: [] x-producteurs: - diapason # Les serveurs des teneurs de compte ne sont pas recensables : un # retrait de version majeure relève du préavis contractuel, jamais d'un décompte. x-consommateurs: []
defaultContentType: application/cloudevents+json
servers: serveurDuTeneur: host: '{hote-du-teneur}' protocol: https description: >- **C'est le teneur de compte qui héberge le point de réception**, et la passerelle qui appelle : la livraison est une requête `POST` sortante vers l'URL que le teneur a enregistrée. L'URL, le secret de signature et les canaux souscrits sont attachés au client OAuth du teneur et déclarés hors de ce contrat. variables: hote-du-teneur: default: reception.teneur.exemple description: L'hôte que le teneur expose pour recevoir les événements.
channels: epargnant: address: /diapason/v1/evenements/epargnant title: Les faits qui concernent un épargnant description: >- Le canal que souscrit le portail des épargnants. Le `subject` de chaque événement est l'identifiant publié de l'épargnant concerné : il désigne la ressource, il n'autorise rien — vérifier que la personne à qui le portail montre le fait en est bien la titulaire reste une obligation du teneur. messages: versementExecute: $ref: '#/components/messages/versementExecute' arbitrageExecute: $ref: '#/components/messages/arbitrageExecute' operationRefusee: $ref: '#/components/messages/operationRefusee' avoirsDevenusDisponibles: $ref: '#/components/messages/avoirsDevenusDisponibles' documentMisADisposition: $ref: '#/components/messages/documentMisADisposition'
entreprise: address: /diapason/v1/evenements/entreprise title: Les faits qui concernent une entreprise description: >- Le canal que souscrit le portail des correspondants. Le `subject` de chaque événement est l'identifiant publié de l'entreprise concernée. messages: operationCollectiveDeclinee: $ref: '#/components/messages/operationCollectiveDeclinee' operationCollectiveExecutee: $ref: '#/components/messages/operationCollectiveExecutee' transmissionDePopulationTraitee: $ref: '#/components/messages/transmissionDePopulationTraitee' restitutionMiseADisposition: $ref: '#/components/messages/restitutionMiseADisposition'
operations: livrerLesEvenementsDeLEpargnant: action: send channel: $ref: '#/channels/epargnant' summary: La passerelle livre au serveur du teneur les faits qui concernent ses épargnants. description: >- **Au moins une fois, jamais exactement une fois.** Le même événement peut être livré plusieurs fois : le destinataire déduplique sur l'attribut `id`, qui ne change pas d'une livraison à l'autre.
**Aucun ordre n'est garanti** — ni entre canaux, ni entre deux faits d'une même ressource. L'extension `sequence` est strictement croissante **par sujet** : elle permet de reconnaître un doublon, un message en retard ou un manque. Un destinataire qui présume l'ordre sans le vérifier se trompera ; celui qui doute relit l'état par le contrat REST, qui fait foi.
**Le teneur répond `2xx` pour accuser réception**, et rien d'autre n'est interprété comme un succès. Tout autre code, comme une absence de réponse, déclenche des réessais espacés par un retrait exponentiel ; après épuisement, le canal est **mis en sommeil** et le teneur en est averti hors bande. Un canal en sommeil ne perd pas les faits survenus entre-temps : il les rattrape à sa reprise, dans un ordre qui reste non garanti. messages: - $ref: '#/channels/epargnant/messages/versementExecute' - $ref: '#/channels/epargnant/messages/arbitrageExecute' - $ref: '#/channels/epargnant/messages/operationRefusee' - $ref: '#/channels/epargnant/messages/avoirsDevenusDisponibles' - $ref: '#/channels/epargnant/messages/documentMisADisposition'
livrerLesEvenementsDeLEntreprise: action: send channel: $ref: '#/channels/entreprise' summary: La passerelle livre au serveur du teneur les faits qui concernent les entreprises qu'il sert. description: >- Mêmes garanties que le canal de l'épargnant : au moins une fois, aucun ordre garanti, `sequence` croissante par sujet, accusé `2xx`, réessais puis mise en sommeil. messages: - $ref: '#/channels/entreprise/messages/operationCollectiveDeclinee' - $ref: '#/channels/entreprise/messages/operationCollectiveExecutee' - $ref: '#/channels/entreprise/messages/transmissionDePopulationTraitee' - $ref: '#/channels/entreprise/messages/restitutionMiseADisposition'
components: messages: versementExecute: name: diapason.versement.execute title: Versement exécuté summary: >- Un versement volontaire est investi — c'est la fin de la chaîne qu'un `202` avait ouverte. Le portail cesse ici de suivre l'opération. contentType: application/cloudevents+json payload: $ref: '#/components/schemas/versementExecute' headers: $ref: '#/components/schemas/enTetesDeLivraison'
arbitrageExecute: name: diapason.arbitrage.execute title: Arbitrage exécuté summary: La composition a changé — aucun fonds n'est sorti. contentType: application/cloudevents+json payload: $ref: '#/components/schemas/arbitrageExecute' headers: $ref: '#/components/schemas/enTetesDeLivraison'
operationRefusee: name: diapason.operation.refusee title: Opération refusée summary: >- Une opération engagée par le portail n'ira pas à son terme. Le motif est **décidé** et destiné à être affiché ; il ne nomme jamais une mesure de conformité. contentType: application/cloudevents+json payload: $ref: '#/components/schemas/operationRefusee' headers: $ref: '#/components/schemas/enTetesDeLivraison'
avoirsDevenusDisponibles: name: diapason.avoirs.devenus-disponibles title: Avoirs devenus disponibles summary: >- Une part d'avoirs a cessé d'être indisponible. Le fait est servi **décidé** — un montant, un dispositif — sans dire ce qui le levait : une échéance atteinte et une contrainte tombée produisent le même fait, parce qu'un portail n'a pas à les distinguer pour prévenir son porteur. contentType: application/cloudevents+json payload: $ref: '#/components/schemas/avoirsDevenusDisponibles' headers: $ref: '#/components/schemas/enTetesDeLivraison'
documentMisADisposition: name: diapason.document.mis-a-disposition title: Document mis à disposition summary: Un relevé, un avis d'opéré ou un document d'information clé est consultable. contentType: application/cloudevents+json payload: $ref: '#/components/schemas/documentMisADisposition' headers: $ref: '#/components/schemas/enTetesDeLivraison'
operationCollectiveDeclinee: name: diapason.operation-collective.declinee title: Opération collective déclinée summary: >- La répartition est arrêtée et déclinée en opérations individuelles. C'est le point où le correspondant sait que sa déclaration a produit ses effets. contentType: application/cloudevents+json payload: $ref: '#/components/schemas/operationCollectiveDeclinee' headers: $ref: '#/components/schemas/enTetesDeLivraison'
operationCollectiveExecutee: name: diapason.operation-collective.executee title: Opération collective exécutée summary: L'exécution est achevée — les avoirs sont investis chez les bénéficiaires. contentType: application/cloudevents+json payload: $ref: '#/components/schemas/operationCollectiveExecutee' headers: $ref: '#/components/schemas/enTetesDeLivraison'
transmissionDePopulationTraitee: name: diapason.transmission-de-population.traitee title: Transmission de population traitée summary: >- Le contrôle d'une transmission est terminé. Le fait porte le décompte des écarts, pas les écarts eux-mêmes : ils se lisent au contrat REST, qui les tient à jour. contentType: application/cloudevents+json payload: $ref: '#/components/schemas/transmissionDePopulationTraitee' headers: $ref: '#/components/schemas/enTetesDeLivraison'
restitutionMiseADisposition: name: diapason.restitution.mise-a-disposition title: Restitution mise à disposition summary: >- Une restitution prévue par la convention de tenue de compte est arrêtée et consultable. contentType: application/cloudevents+json payload: $ref: '#/components/schemas/restitutionMiseADisposition' headers: $ref: '#/components/schemas/enTetesDeLivraison'
schemas: enTetesDeLivraison: type: object description: >- Les en-têtes HTTP de la livraison. La liaison est la **liaison HTTP de CloudEvents en mode structuré** : l'enveloppe entière est le corps JSON, et ces en-têtes ne portent que ce qui doit être lu avant de faire confiance au corps. required: [Content-Type, Diapason-Signature, Diapason-Signature-Horodatage] properties: Content-Type: type: string const: application/cloudevents+json; charset=utf-8 Diapason-Signature: type: string description: >- La signature du corps — HMAC-SHA256, clé partagée avec le teneur à l'enregistrement, encodée en hexadécimal. **Le destinataire la vérifie avant de lire le corps** : sans elle, n'importe qui connaissant l'URL pourrait fabriquer un fait et le faire afficher à un épargnant. Diapason-Signature-Horodatage: type: string format: date-time description: >- L'instant de la signature, couvert par elle. Un destinataire rejette une livraison trop ancienne : c'est ce qui empêche de rejouer indéfiniment une livraison interceptée.
socleDEvenement: type: object description: >- L'enveloppe **CloudEvents 1.0.2** commune à tous les messages. Les consommateurs sont tolérants : un attribut inconnu s'ignore, et l'apparition d'un attribut nouveau n'est pas une rupture. required: [specversion, id, source, type, subject, time, sequence, versioncontrat, data] properties: specversion: type: string const: '1.0' id: type: string minLength: 1 description: >- L'identifiant unique du fait — **la clé de déduplication**. Il ne change pas d'une livraison à l'autre, et deux livraisons du même `id` désignent le même fait, jamais deux faits semblables. source: type: string format: uri description: >- L'origine de l'événement : l'URI de la passerelle qui l'émet. Elle ne nomme jamais le domaine où le fait est né. type: type: string description: Le type du fait, dans la liste fermée des messages de ce contrat. subject: type: string minLength: 1 description: >- La ressource concernée — l'identifiant publié de l'épargnant sur le canal de l'épargnant, de l'entreprise sur celui de l'entreprise. Il **désigne**, il n'autorise pas. time: type: string format: date-time description: L'instant de survenance du fait (UTC) — jamais celui de la livraison. sequence: type: integer minimum: 1 description: >- Extension CloudEvents : le rang du fait **pour ce sujet**, strictement croissant. Un trou signale un manque, un rang déjà vu un doublon, un rang inférieur au dernier connu un message en retard. versioncontrat: type: string description: Extension — la version sémantique du présent contrat. correlationid: type: string description: >- Extension — l'identifiant de corrélation de l'écriture qui a déclenché ce fait, restitué tel que le teneur l'avait fourni. Absent quand le fait ne vient d'aucune écriture du portail : une échéance qui tombe n'a pas d'appelant. datacontenttype: type: string const: application/json data: type: object description: La charge utile, propre à chaque type de fait.
versementExecute: allOf: - $ref: '#/components/schemas/socleDEvenement' - type: object properties: type: { const: diapason.versement.execute } data: type: object required: [operation, dispositif, montant_net_ct, execute_le] properties: operation: type: string description: La référence servie par le `202` de la demande de versement. dispositif: { type: string } montant_net_ct: type: integer description: >- Le net investi, en centimes — servi tel qu'il est porté, jamais recalculé. execute_le: { type: string, format: date }
arbitrageExecute: allOf: - $ref: '#/components/schemas/socleDEvenement' - type: object properties: type: { const: diapason.arbitrage.execute } data: type: object required: [operation, dispositif, execute_le] properties: operation: { type: string } dispositif: { type: string } execute_le: { type: string, format: date }
operationRefusee: allOf: - $ref: '#/components/schemas/socleDEvenement' - type: object properties: type: { const: diapason.operation.refusee } data: type: object required: [operation, nature, motif, refusee_le] properties: operation: { type: string } nature: type: string description: La nature de l'opération refusée, dans la nomenclature du contrat REST. motif: type: string description: >- Le motif **décidé**, destiné à être affiché tel quel au porteur. Il ne nomme jamais une mesure de conformité, et ne livre aucun paramètre dont un portail reconstituerait la règle. refusee_le: { type: string, format: date }
avoirsDevenusDisponibles: allOf: - $ref: '#/components/schemas/socleDEvenement' - type: object properties: type: { const: diapason.avoirs.devenus-disponibles } data: type: object required: [dispositif, montant_ct, disponible_depuis] properties: dispositif: { type: string } montant_ct: type: integer description: Le montant devenu disponible, en centimes, à la date du fait. disponible_depuis: { type: string, format: date }
documentMisADisposition: allOf: - $ref: '#/components/schemas/socleDEvenement' - type: object properties: type: { const: diapason.document.mis-a-disposition } data: type: object required: [document, type_de_document, emis_le] properties: document: type: string description: L'identifiant servi par la liste des documents du contrat REST. type_de_document: type: string enum: - RELEVE_DE_SITUATION - AVIS_D_OPERE - DOCUMENT_D_INFORMATION_CLE - REGLEMENT_DU_DISPOSITIF emis_le: { type: string, format: date } dispositif: { type: string }
operationCollectiveDeclinee: allOf: - $ref: '#/components/schemas/socleDEvenement' - type: object properties: type: { const: diapason.operation-collective.declinee } data: type: object required: [operation, dispositif, beneficiaires, declinee_le] properties: operation: type: string description: La référence servie par le `202` de la déclaration. dispositif: { type: string } beneficiaires: type: integer description: Le nombre de bénéficiaires de la répartition arrêtée. montant_reparti_ct: { type: integer } declinee_le: { type: string, format: date-time }
operationCollectiveExecutee: allOf: - $ref: '#/components/schemas/socleDEvenement' - type: object properties: type: { const: diapason.operation-collective.executee } data: type: object required: [operation, dispositif, execute_le] properties: operation: { type: string } dispositif: { type: string } execute_le: { type: string, format: date } anomalies: type: integer description: >- Le nombre d'anomalies retenues ; leur nature se lit au contrat REST. Zéro signifie que rien n'attend le teneur.
transmissionDePopulationTraitee: allOf: - $ref: '#/components/schemas/socleDEvenement' - type: object properties: type: { const: diapason.transmission-de-population.traitee } data: type: object required: [transmission, mouvements_recus, mouvements_appliques, ecarts, traitee_le] properties: transmission: type: string description: La référence servie par le `202` de la transmission. mouvements_recus: { type: integer } mouvements_appliques: { type: integer } ecarts: type: integer description: >- Le **décompte** des écarts. Les écarts eux-mêmes se lisent au contrat REST : les republier ici les figerait dans une charge utile pendant que le teneur les corrige. traitee_le: { type: string, format: date-time }
restitutionMiseADisposition: allOf: - $ref: '#/components/schemas/socleDEvenement' - type: object properties: type: { const: diapason.restitution.mise-a-disposition } data: type: object required: [restitution, type_de_restitution, arretee_le] properties: restitution: { type: string } type_de_restitution: type: string enum: [CAMPAGNE, ENCOURS, FRAIS] arretee_le: { type: string, format: date-time } retenue: type: boolean description: >- Vrai quand le contenu sera retenu à la lecture — population sous le plancher d'agrégation du teneur. Le portail sait ainsi qu'il ne sert à rien de proposer l'ouverture.