Concepts

Sessions auditables

Une session est l'unité atomique de validation : elle commence par une authentification (OTP SMS ou TOTP), enregistre N actions signées, et se clôture avec un récap cryptographique.

Machine à états

                  POST /v1/sessions
                         │
                         ▼
                  ┌─────────────┐       OTP × 3 invalides
                  │ PENDING_OTP │ ───────────────────────┐
                  └─────────────┘                        │
                         │  POST /validate               │
                         ▼                               ▼
                  ┌─────────────┐    POST /refuse  ┌────────────┐
                  │    OPEN     │ ───────────────▶ │ ABANDONED  │
                  └─────────────┘                  └────────────┘
                         │                               │
                         │  POST /actions × N            │   entrée REFUSED
                         │  POST /artifacts (option)     │   co-signée dans
                         ▼                               │   la chaîne
                  ┌─────────────┐                        │
                  │   CLOSED    │                        │
                  └─────────────┘                        │
                         │                               │
                         │  POST /v1/proofs              │
                         ▼                               ▼
                  bundle.asice                    bundle.asice
                  (verdict MATCH/PARTIAL/...)     (avec entry REFUSED)
        

ABANDONED est un terminal alternatif à CLOSED. Une session ABANDONED a quand même un bundle de preuve générable : il prouve que l'utilisateur a explicitement décliné ou que l'OTP a échoué 3 fois. C'est une preuve opposable autant qu'un MATCH.

Modes : sync vs async (magic-link)

Sync (par défaut)

L'organisation pilote intégralement le flow : il appelle open, transmet l'OTP à son utilisateur, appelle validate + actions + close séquentiellement.

Async (magic-link)

Pour les cas où l'utilisateur valide sur un device séparé : tu passes callback_url + consent_content à l'open, le service renvoie un magic_link_url. Quand l'utilisateur valide via /consent/{token}, un webhook HMAC-signé est posté sur ton callback_url.

Types d'événements (EventType)

Chaque entrée de la chaîne d'audit porte un type d'event : il dit quelle nature d'acte l'entrée enregistre. C'est le champ event_type que tu passes à POST /v1/sessions/{id}/actions, validé contre la liste fermée ci-dessous — un type inconnu est rejeté.

À quoi ça sert : classer et filtrer les entrées (timeline, bundle lisible), piloter les règles métier (certains types exigent une session OPEN, cf. colonne ci-dessous), et donner à un tiers une lecture immédiate de ce qui s'est passé, acte par acte.

Type Requiert session OPEN ? Quand
LOGIN—Émis automatiquement après validate OTP réussi.
VALIDATION_ACTION✓L'utilisateur valide un document, un consentement, une décision.
SEAL_REQUEST✓Demande de scellement envoyée à un prestataire externe (Yousign, DocuSign).
SEAL_COMPLETED—Le prestataire confirme que le sceau est apposé.
ARTIFACT_ATTACHED✓Lien d'un PDF scellé externe à la chaîne (artifact_hash = payload_hash de l'entrée précédente).
DOCUMENT_READ—Trace de lecture d'un document, optionnellement liée à une session.
EMAIL_SENT—Envoi d'email opérationnel à journaliser.
SESSION_PLAN_COMMITTED✓S41 : scellement du manifest prévisionnel à validate OTP.
SESSION_PLAN_RESOLVED✓S41 : verdict comparatif émis à la clôture.
SIGNING_CONTEXT_ATTESTED✓S49 : mandat OWN/DELEGATED scellé à validate OTP.
REFUSED✓S55 : refus explicite (user_explicit) OU auto-déclenché sur otp_max_attempts. payload_hash = sha256(session_id|REFUSED|reason). Session passe à ABANDONED.

Opération, type d'event, event webhook — ne pas confondre

Trois notions portent le mot « type ». Elles vivent à des moments différents de la vie d'une session :

Notion C'est quoi À quoi ça sert
Opération
prévu
Une ligne du manifest prévisionnel : {op_id, type, payload_hash}. Une intention déclarée à l'ouverture, scellée à l'OTP. Délimiter le périmètre attendu et obtenir un verdict (MATCH/PARTIAL/DIVERGENT) à la clôture.
Type d'event
réel
Le event_type (EventType) d'une entrée effectivement écrite dans la chaîne — la liste fermée ci-dessus. Classer et qualifier chaque acte immuable du journal d'audit.
Event webhook
notification
Le message session.* / anchor.* que ProofIt pousse à ton serveur quand un état change. Réagir en temps réel côté SI, sans polling.

Comment ils se relient : pour réaliser une opération prévue, tu enregistres une action avec son event_type (réel) et metadata.op_id qui pointe l'opération. Le type de l'opération annonce l'event_type attendu ; à la clôture, op_id + payload_hash sont confrontés au plan pour calculer le verdict. L'action écrite déclenche alors l'event webhook session.action_recorded.

Refuser explicitement (S55)

L'organisation ou l'utilisateur peut refuser une session à tout moment avant son CLOSED. Cas d'usage :

  • Bouton "Refuser" sur la page magic-link /consent/{token}
  • Annulation côté SI organisation après vérification fraude
  • Auto-déclenchement après 3 OTP invalides (reason=otp_max_attempts)
POST /v1/sessions/{id}/refuse
Authorization: Bearer eyJ...
X-Organisation-Id: 01900000-...
Content-Type: application/json

{ "reason": "user_explicit" }     // libre, ex: organisation_revoked, fraud_detected…

Idempotent : repasser refuse sur une session déjà ABANDONED → 200 sans changement. Une session CLOSED ne peut pas être ABANDONED (422 SESSION_CLOSED).

Champs émis à l'ouverture

  • session_id (UUIDv7, ordre temporel garanti)
  • session_open_hash = sha256(session_id ‖ organisation_id ‖ actor_user_id ‖ opened_at ‖ channel) — affiché dans le mail témoin + bénéficiaire
  • magic_link_url (si mode async)
  • planned_root_hash + planned_operation_count (si manifest fourni)

Champs émis à la clôture

  • session_root_hash = hash agrégé des entrées de la session — affiché dans le mail récap + dans le bundle
  • executed_root_hash + planned_verdict (si manifest)
  • Mail récap DKIM-signé envoyé au beneficiary_email (chiffré at-rest libsodium secretbox)