signaletique-salarie
Interface synchrone (OpenAPI) — version 0.1.0. Producteur : entreprise. Consommateurs déclarés : back-office, traitement-dsn.
Le service coordonne deux domaines sans modèle partagé ni transaction distribuée : Entreprise détient le lien d’emploi et les données sociales, Épargnant détient l’identité. La réponse est asynchrone (202 et operationId) : l’accusé ne dépend pas de la disponibilité de l’Épargnant. Un lien d’emploi n’est jamais créé sans epargnantId confirmé ; un rapprochement ambigu suspend la création. Les parties personne, emploi et données sociales ont des résultats indépendants.
La spécification
Section intitulée « La spécification »openapi: 3.1.0info: title: entreprise — la mise à jour de la signalétique salarié version: 0.1.0 summary: >- Enregistrer ou mettre à jour un salarié — unitairement ou par fichier —, suivre l'opération, et corriger un mauvais rattachement. description: >- Le service coordonne deux domaines sans modèle partagé ni transaction distribuée : Entreprise détient le lien d'emploi et les données sociales, Épargnant détient l'identité. La réponse est asynchrone (202 et operationId) : l'accusé ne dépend pas de la disponibilité de l'Épargnant. Un lien d'emploi n'est jamais créé sans epargnantId confirmé ; un rapprochement ambigu suspend la création. Les parties personne, emploi et données sociales ont des résultats indépendants. x-producteurs: - entreprise x-consommateurs: - composant: back-office statut: pressenti — assistant signalétique et dialogue d'ajout d'un salarié - composant: traitement-dsn statut: pressenti — canal amont, hors plateforme x-ruptures: [] # première version publiée — aucune rupturepaths: /entreprise/v1/entreprises/{entrepriseId}/salaries:upsert: post: operationId: enregistrerOuMettreAJourUnSalarie summary: >- Enregistrer ou mettre à jour un salarié — accusé asynchrone, l'opération se poursuit et se consulte. description: >- Au moins personneDeclaree ou emploi doit être présent. Pour créer un lien sans epargnantId connu, les attributs minimaux de résolution sont exigés. Le tenant vient du contexte sécurisé, jamais du corps. security: - authentification: [entreprise:signaletique] parameters: - $ref: '#/components/parameters/tenant' - $ref: '#/components/parameters/correlation' - $ref: '#/components/parameters/idempotence' - name: entrepriseId in: path required: true description: L'entreprise portant le lien d'emploi — résolue dans le tenant courant. schema: { type: string, minLength: 1 } requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DemandeDeSignaletique' responses: '202': description: >- La demande est validée et enregistrée ; le traitement se poursuit. Le rejeu d'une même clé et d'un même corps retourne cette même réponse. headers: Location: description: Le lien de suivi de l'opération. schema: { type: string } content: application/json: schema: $ref: '#/components/schemas/AccuseDOperation' '400': { $ref: '#/components/responses/demandeInvalide' } '401': { $ref: '#/components/responses/nonAuthentifie' } '403': { $ref: '#/components/responses/nonAutorise' } '404': { $ref: '#/components/responses/introuvable' } '409': { $ref: '#/components/responses/conflit' } '422': { $ref: '#/components/responses/regleMetier' } '429': { $ref: '#/components/responses/quota' } '503': { $ref: '#/components/responses/indisponible' } /entreprise/v1/operations/mises-a-jour-signaletique-salarie/{operationId}: get: operationId: consulterUneOperationDeSignaletique summary: L'état de l'opération, ses résultats par partie et ses anomalies communicables. description: >- La réponse n'expose ni les candidats d'un rapprochement, ni une donnée personnelle que l'appelant n'est pas autorisé à consulter. security: - authentification: [entreprise:consultation] parameters: - $ref: '#/components/parameters/tenant' - $ref: '#/components/parameters/correlation' - name: operationId in: path required: true schema: { type: string, minLength: 1 } responses: '200': description: L'état courant de l'opération. content: application/json: schema: $ref: '#/components/schemas/EtatDOperation' '401': { $ref: '#/components/responses/nonAuthentifie' } '403': { $ref: '#/components/responses/nonAutorise' } '404': { $ref: '#/components/responses/introuvable' } /entreprise/v1/liens-emploi/{lienEmploiId}:corriger-rattachement: post: operationId: corrigerLeRattachementDUnLienEmploi summary: >- Corriger le rattachement d'un lien d'emploi à une autre personne — droit renforcé, historisé, sans déplacement silencieux des effets aval. description: >- Vérifie que le lien et les deux personnes appartiennent au même tenant, verrouille la version courante du lien, conserve l'ancien et le nouveau rattachement, publie un événement métier et déclenche l'analyse des conséquences aval sans les corriger. Les comptes, adhésions, ordres et positions issus de l'ancien rattachement ne sont jamais déplacés par cette seule commande. security: - authentification: [entreprise:correction-rattachement] parameters: - $ref: '#/components/parameters/tenant' - $ref: '#/components/parameters/correlation' - $ref: '#/components/parameters/idempotence' - name: lienEmploiId in: path required: true schema: { type: string, minLength: 1 } requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DemandeDeCorrectionDeRattachement' responses: '200': description: La correction est enregistrée et historisée. content: application/json: schema: $ref: '#/components/schemas/AccuseDOperation' '400': { $ref: '#/components/responses/demandeInvalide' } '401': { $ref: '#/components/responses/nonAuthentifie' } '403': { $ref: '#/components/responses/nonAutoriseCorrection' } '404': { $ref: '#/components/responses/introuvable' } '409': { $ref: '#/components/responses/conflit' } '422': { $ref: '#/components/responses/regleMetier' } /entreprise/v1/imports/signaletiques-salaries: post: operationId: creerUnImportDeSignaletiques summary: >- Créer un import et annoncer son périmètre — une entreprise, ou un groupe dont chaque ligne désignera son entreprise membre. security: - authentification: [entreprise:signaletique] parameters: - $ref: '#/components/parameters/tenant' - $ref: '#/components/parameters/correlation' - $ref: '#/components/parameters/idempotence' requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/DemandeDImport' responses: '201': description: >- L'import est créé, en attente du fichier. Le rejeu d'une même clé de fichier et d'une même empreinte retourne l'import initial (RM-026). content: application/json: schema: $ref: '#/components/schemas/EtatDImport' '400': { $ref: '#/components/responses/demandeInvalide' } '401': { $ref: '#/components/responses/nonAuthentifie' } '403': { $ref: '#/components/responses/nonAutorise' } '404': { $ref: '#/components/responses/introuvable' } '409': { $ref: '#/components/responses/conflit' } '413': { $ref: '#/components/responses/fichierTropGrand' } '415': { $ref: '#/components/responses/formatNonSupporte' } '422': { $ref: '#/components/responses/regleMetier' } /entreprise/v1/imports/signaletiques-salaries/{importId}:sceller: post: operationId: scellerUnImport summary: >- Sceller l'import après l'envoi des octets — le contenu devient immuable ; un nouvel envoi exige un nouvel import. description: >- Le scellement vérifie la taille observée, l'empreinte, le format, l'absence de contenu dangereux et la cohérence avec les métadonnées annoncées. Une anomalie fatale rejette l'import avant toute exécution métier. security: - authentification: [entreprise:signaletique] parameters: - $ref: '#/components/parameters/tenant' - $ref: '#/components/parameters/correlation' - $ref: '#/components/parameters/idempotence' - name: importId in: path required: true schema: { type: string, minLength: 1 } requestBody: required: true content: application/json: schema: type: object required: [empreinteSha256] properties: empreinteSha256: type: string description: L'empreinte des octets envoyés — l'identité du contenu. responses: '200': description: L'import est scellé et validé, ou rejeté par un contrôle global. content: application/json: schema: $ref: '#/components/schemas/EtatDImport' '400': { $ref: '#/components/responses/demandeInvalide' } '401': { $ref: '#/components/responses/nonAuthentifie' } '403': { $ref: '#/components/responses/nonAutorise' } '404': { $ref: '#/components/responses/introuvable' } '409': { $ref: '#/components/responses/conflit' } '422': { $ref: '#/components/responses/regleMetier' } /entreprise/v1/imports/signaletiques-salaries/{importId}: get: operationId: consulterUnImport summary: >- Les compteurs de l'import au moment de la requête — agrégés depuis les partitions, jamais un rechargement des lignes. security: - authentification: [entreprise:consultation] parameters: - $ref: '#/components/parameters/tenant' - $ref: '#/components/parameters/correlation' - name: importId in: path required: true schema: { type: string, minLength: 1 } responses: '200': description: L'état et les compteurs de l'import. content: application/json: schema: $ref: '#/components/schemas/EtatDImport' '401': { $ref: '#/components/responses/nonAuthentifie' } '403': { $ref: '#/components/responses/nonAutorise' } '404': { $ref: '#/components/responses/introuvable' } /entreprise/v1/imports/signaletiques-salaries/{importId}/lignes: get: operationId: listerLesLignesDUnImport summary: Les lignes de l'import, filtrables par statut et paginées par curseur. security: - authentification: [entreprise:consultation] parameters: - $ref: '#/components/parameters/tenant' - $ref: '#/components/parameters/correlation' - name: importId in: path required: true schema: { type: string, minLength: 1 } - name: statut in: query required: false schema: $ref: '#/components/schemas/StatutDeLigne' - name: curseur in: query required: false description: Le numéro de ligne après lequel poursuivre. schema: { type: integer, format: int64 } - name: limite in: query required: false schema: { type: integer, default: 100, maximum: 1000 } responses: '200': description: Une page de lignes — jamais la totalité. content: application/json: schema: type: array items: $ref: '#/components/schemas/LigneDImport' '401': { $ref: '#/components/responses/nonAuthentifie' } '403': { $ref: '#/components/responses/nonAutorise' } '404': { $ref: '#/components/responses/introuvable' }components: parameters: tenant: name: X-Tenant-Id in: header required: true description: >- Le teneur de compte — injecté et signé par la passerelle, dérivé de l'identité authentifiée. Il n'est jamais fourni comme valeur libre dans l'URL ou le corps. schema: { type: string, minLength: 1 } correlation: name: X-Correlation-Id in: header required: true description: La corrélation de bout en bout — propagée jusqu'aux événements. schema: { type: string, minLength: 1 } idempotence: name: Idempotency-Key in: header required: true description: >- La clé d'idempotence, de portée tenant + appelant + route + clé. Même clé et même corps : la réponse initiale. Même clé, corps différent : 409. schema: { type: string, minLength: 1 } securitySchemes: authentification: type: http scheme: bearer description: >- Tout appel est authentifié (401) et autorisé par famille d'accès (403 hors famille), déclarée en portée entreprise:famille. Trois familles ici : entreprise:signaletique (les commandes et les imports), entreprise:consultation (le suivi) et entreprise:correction-rattachement — un droit renforcé, distinct des deux autres, comme le § 18 l'exige. responses: demandeInvalide: description: JSON invalide ou champ obligatoire absent (INVALID_REQUEST). content: application/problem+json: schema: { $ref: '#/components/schemas/Probleme' } nonAuthentifie: description: Identité absente ou invalide (UNAUTHENTICATED). nonAutorise: description: >- Droit insuffisant (FORBIDDEN) — les 403 croisés entre familles sont prouvés par les tests d'assemblage. nonAutoriseCorrection: description: >- La correction de rattachement exige la famille entreprise:correction-rattachement — ni entreprise:signaletique ni entreprise:consultation ne l'ouvrent. introuvable: description: >- Ressource absente dans le tenant courant — y compris une ressource d'un autre tenant, dont l'existence n'est jamais confirmée (404, jamais 403). content: application/problem+json: schema: { $ref: '#/components/schemas/Probleme' } conflit: description: >- IDEMPOTENCY_KEY_REUSED (même clé, corps différent), SOURCE_VERSION_CONFLICT (version incompatible) ou conflit d'empreinte d'import. content: application/problem+json: schema: { $ref: '#/components/schemas/Probleme' } regleMetier: description: >- BUSINESS_RULE_VIOLATION ou IMPORT_SCOPE_VIOLATION — demande syntaxiquement valide mais incohérente au regard du métier ou du périmètre annoncé. content: application/problem+json: schema: { $ref: '#/components/schemas/Probleme' } quota: description: Capacité momentanément dépassée (RATE_LIMITED). fichierTropGrand: description: Taille de fichier ou de ligne supérieure à la limite (FILE_TOO_LARGE). formatNonSupporte: description: Format ou encodage non pris en charge (UNSUPPORTED_FILE_FORMAT). indisponible: description: >- Demande non enregistrable ; le client peut rejouer (SERVICE_UNAVAILABLE). Une indisponibilité survenant APRÈS l'enregistrement n'est jamais rendue ici : elle conduit à ECHEC_REPRENABLE. schemas: DemandeDeSignaletique: type: object required: [source, modeMiseAJour, dateEffet] description: >- Au moins personneDeclaree ou emploi est présent. Les montants sont des chaînes décimales ; les dates sont ISO 8601 et les instants portent leur décalage. properties: source: $ref: '#/components/schemas/Source' personneDeclaree: $ref: '#/components/schemas/PersonneDeclaree' emploi: $ref: '#/components/schemas/Emploi' donneesSociales: $ref: '#/components/schemas/DonneesSociales' modeMiseAJour: $ref: '#/components/schemas/ModeMiseAJour' dateEffet: type: string format: date description: La date à laquelle le fait métier est valable. Source: type: object required: [systeme, referenceSalarie, version] properties: systeme: type: string description: La source déclarée et autorisée — par exemple DSN, SIRH_GROUPE. emetteurId: type: string referenceSalarie: type: string description: L'identifiant stable du salarié dans la source (matricule). version: type: string description: La version ou séquence comparable selon la politique de la source. dateObservation: type: string format: date-time PersonneDeclaree: type: object description: >- Les données transmises au domaine Épargnant pour la résolution. Elles ne deviennent jamais des colonnes métier de personne dans Entreprise. properties: nomNaissance: { type: string } nomUsage: { type: string } prenoms: type: array items: { type: string } dateNaissance: { type: string, format: date } lieuNaissance: type: object properties: codePays: { type: string } codeCommune: { type: string } identifiantsDeclares: type: array description: >- Un identifiant sensible (NIR) est chiffré au repos et masqué dans les interfaces et les journaux ; il n'apparaît jamais en clair. items: type: object required: [type, valeur] properties: type: { type: string } valeur: { type: string } niveauVerification: { type: string } Emploi: type: object properties: etablissementId: { type: string } matricule: { type: string } natureLien: { type: string } dateDebut: { type: string, format: date } dateFin: type: [string, 'null'] format: date description: >- Une date de fin ne supprime pas le lien : elle clôt sa période (RM-018). statut: { type: string } categorie: { type: string } college: { type: string } DonneesSociales: type: object properties: periode: type: string description: La période de référence, par exemple 2026-07. remunerationBrute: type: object properties: montant: type: string description: Chaîne décimale — jamais un flottant, pour éviter la perte de précision. devise: { type: string } ModeMiseAJour: type: string enum: [DELTA, FULL_SNAPSHOT] description: >- En DELTA, un champ absent ne modifie rien et un null explicite efface si le champ l'autorise. En FULL_SNAPSHOT, le contenu est l'état complet connu de la source pour le périmètre annoncé. Dans les deux modes, une absence ne supprime jamais implicitement une personne ni un lien d'emploi. AccuseDOperation: type: object required: [operationId, statut, recuLe] properties: operationId: { type: string } statut: $ref: '#/components/schemas/StatutDOperation' recuLe: { type: string, format: date-time } liens: type: object properties: statut: { type: string } StatutDOperation: type: string enum: - RECU - VALIDE - RESOLUTION_PERSONNE - ARBITRAGE_REQUIS - APPLICATION - TERMINE - TERMINE_PARTIELLEMENT - REJETE - ECHEC_REPRENABLE - ECHEC_DEFINITIF StatutDePartie: type: string enum: [ABSENTE, APPLIQUE, SANS_CHANGEMENT, EN_ATTENTE, REJETEE] EtatDOperation: type: object required: [operationId, statut, recuLe] properties: operationId: { type: string } statut: $ref: '#/components/schemas/StatutDOperation' source: $ref: '#/components/schemas/Source' resultats: type: object description: >- Les trois parties ont des résultats indépendants : une modification d'emploi valide n'est pas annulée parce qu'un changement d'identité attend un justificatif. properties: personne: type: object properties: statut: { $ref: '#/components/schemas/StatutDePartie' } epargnantId: { type: string } emploi: type: object properties: statut: { $ref: '#/components/schemas/StatutDePartie' } lienEmploiId: { type: string } version: { type: integer } donneesSociales: type: object properties: statut: { $ref: '#/components/schemas/StatutDePartie' } anomalies: type: array items: type: object properties: code: { type: string } message: { type: string } actionAttendue: { type: string } recuLe: { type: string, format: date-time } misAJourLe: { type: string, format: date-time } DemandeDeCorrectionDeRattachement: type: object required: [nouvelEpargnantId, dateEffet, motif] properties: nouvelEpargnantId: { type: string } dateEffet: { type: string, format: date } motif: type: string description: Le code de justification — par exemple ERREUR_DE_RAPPROCHEMENT. justification: { type: string } resolutionCaseId: { type: string } DemandeDImport: type: object required: [source, perimetre, format, modeMiseAJour] properties: source: $ref: '#/components/schemas/Source' perimetre: $ref: '#/components/schemas/PerimetreDImport' format: type: string enum: [CSV_UTF8, NDJSON_UTF8] description: >- Des formats à lecture séquentielle seulement — CSV_UTF8 est obligatoire. Le service ne construit jamais un arbre complet en mémoire. modeMiseAJour: $ref: '#/components/schemas/ModeMiseAJour' nomFichier: { type: string } tailleDeclaree: { type: integer, format: int64 } empreinteSha256: { type: string } PerimetreDImport: type: object required: [type] description: >- Un import a exactement un périmètre dans un seul tenant (RM-021). En GROUPE, chaque ligne désigne son entreprise, vérifiée membre à la date de référence ; une entreprise extérieure fait rejeter la ligne, sans jamais élargir le périmètre. properties: type: type: string enum: [ENTREPRISE, GROUPE] entrepriseId: { type: string } groupeId: { type: string } dateReference: { type: string, format: date } StatutDImport: type: string enum: - EN_ATTENTE_FICHIER - RECU - VALIDATION - PRET_A_TRAITER - TRAITEMENT - TERMINE - TERMINE_AVEC_ANOMALIES - REJETE StatutDeLigne: type: string enum: - EN_ATTENTE - RESERVEE - TERMINE - TERMINE_PARTIELLEMENT - ARBITRAGE_REQUIS - REJETE - ECHEC_DEFINITIF EtatDImport: type: object required: [importId, statut] properties: importId: { type: string } statut: $ref: '#/components/schemas/StatutDImport' perimetre: $ref: '#/components/schemas/PerimetreDImport' empreinteSha256: { type: string } nombreDePartitions: { type: integer } compteurs: type: object description: >- Les compteurs par statut de ligne, agrégés depuis les partitions — jamais obtenus en rechargeant les lignes. additionalProperties: type: integer format: int64 LigneDImport: type: object required: [numeroLigne, statut] properties: numeroLigne: { type: integer, format: int64 } statut: $ref: '#/components/schemas/StatutDeLigne' entrepriseId: { type: string } referenceSalarieSource: { type: string } operationId: { type: string } codesAnomalie: type: array items: { type: string } Probleme: type: object description: Le format d'erreur du § 21.2 — application/problem+json. required: [status, code] properties: type: { type: string } title: { type: string } status: { type: integer } code: { type: string } detail: { type: string } instance: { type: string } correlationId: { type: string } errors: type: array items: type: object properties: path: { type: string } code: { type: string }