Aller au contenu

PD-287 — Partage de preuve sans compte par lien PRE, OTP et traçabilité probatoire

1. Objectif

La User Story PD-287 DOIT permettre à un propriétaire de preuve de déléguer à un tiers externe un droit de lecture temporaire, sans création de compte persistant, via un lien sécurisé activé par OTP email, en préservant strictement les invariants zero-knowledge et WORM, avec révocation, expiration, traçabilité probatoire append-only, et export autonome vérifiable optionnel (nice-to-have).

2. Périmètre / Hors périmètre

Inclus

  • Création d’un lien de partage pour une preuve unique.
  • Activation du lien par OTP email.
  • Identité cryptographique éphémère par lien.
  • Délégation cryptographique via PRE (pas de partage de clé propriétaire).
  • Consultation en lecture seule du document + éléments probatoires (hash, Merkle, TSA, ancrage blockchain, métadonnées).
  • Révocation manuelle par le propriétaire.
  • Expiration automatique par TTL.
  • Journal d’accès append-only, scellé et ancré.
  • Liste des liens actifs et historique d’accès par lien côté propriétaire.
  • Export composite autonome au format ZIP (optionnel MVP, activable).
  • Quotas et protections anti-abus.
  • UX anti-phishing minimale.
  • Avertissement explicite “pas de DRM”.

Exclu

  • Création de compte destinataire.
  • Partage entre deux comptes ProbatioVault.
  • Modification/annotation/signature de la preuve par destinataire.
  • Re-partage par le destinataire.
  • Révocation rétroactive d’un accès passé.
  • Duplication ou mutation du document WORM original.
  • Consultation hors-ligne.
  • Lien multi-preuves.
  • Révocation partielle (consultation vs export).
  • Garantie forte de preuve d’absence d’accès (heartbeats signés) : hors périmètre MVP.
  • Décision judiciaire finale d’admissibilité d’une preuve : hors périmètre (non testable en environnement technique).

3. Définitions

Terme Définition
PRE Proxy Re-Encryption, mécanisme de délégation cryptographique sans révélation de clé propriétaire.
Lien de partage URL opaque associée à une preuve et à un destinataire email.
Destinataire Tiers externe sans compte persistant ProbatioVault.
OTP Code temporaire à usage court envoyé par email au destinataire.
Session destinataire Session éphémère après OTP valide, soumise à réauthentification par inactivité.
WORM Write Once Read Many : original immuable, non modifiable/non dupliqué.
Zero-knowledge Le serveur ne lit jamais le contenu déchiffré de la preuve.
État terminal État sans transition sortante autorisée.
Révocation Coupure des accès futurs uniquement.
Expiration Fin automatique des droits à expires_at_utc.
Journal append-only Journal ne permettant que des ajouts d’événements immuables.
Preuve composite exportée ZIP contenant document, artefacts probatoires et manifeste de vérification autonome.
Anti-enumeration Comportements/réponses ne permettant pas de déduire l’existence d’autres ressources.
Open request Requête destinataire en cours (non clôturée) comptabilisée dans la fenêtre anti-abus open_requests_per_ip_per_min.

4. Invariants (non négociables)

ID Règle Justification
INV-287-01 Aucun endpoint, service, log ou dump mémoire listé dans le périmètre de la story ne DOIT contenir le document en clair. Vérification bornée obligatoire par: (a) audit des flux réseau côté serveur, (b) grep mémoire process, © absence du document dans logs/dumps. Zero-knowledge testable et borné.
INV-287-02 La délégation DOIT utiliser PRE ; tout mécanisme de partage par clé en clair est interdit. Contrainte PO explicite.
INV-287-03 L’original WORM ne DOIT jamais être modifié, déplacé ni dupliqué. Valeur probatoire.
INV-287-04 La possession du lien seule ne suffit pas ; OTP valide requis à chaque nouvelle session. Délégation ciblée, non bearer-link.
INV-287-05 Une identité cryptographique éphémère DOIT être unique par lien. Compartimentation inter-partages.
INV-287-06 États autorisés PENDING_ACTIVATION, ACTIVE, OTP_BLOCKED, REVOKED, EXPIRED uniquement. FSM fermée et testable.
INV-287-07 Toute transition non listée en §5.5 est interdite. Zéro ambiguïté.
INV-287-08 Après révocation, toute nouvelle requête d’accès DOIT être refusée dans la borne sla_revocation_effective, où la borne SLA = valeur par défaut configurée (2000ms P95). Révocation effective contractuelle.
INV-287-09 Après expiration TTL, toute nouvelle requête d’accès DOIT être refusée. Expiration effective.
INV-287-10 REVOKED et EXPIRED sont terminaux ; -> * interdit explicitement. Contrat d’état terminal.
INV-287-11 Le destinataire ne DOIT pas pouvoir créer un nouveau partage. Non-propagation des droits.
INV-287-12 Réponses d’erreur destinataire non différenciantes pour lien/token invalide, inconnu, expiré, révoqué ou non autorisé: 404 générique uniquement (hors 429 rate-limit). Anti-enumeration.
INV-287-13 Les événements CREATED/ACTIVATED/VIEWED/EXPORTED/REVOKED/EXPIRED DOIVENT être journalisés append-only avec hash-chain (hash_prev) ; ancrage périodique des lots via Merkle root blockchain. Traçabilité opposable et intégrité chaînée.
INV-287-15 Les données destinataire (email, IP, clés/certificats éphémères, session OTP) DOIVENT être supprimées 90 jours après expiration/révocation du lien. Le journal d’audit DOIT être conservé aussi longtemps que la preuve originale. RGPD minimisation/rétention testable.
INV-287-16 Tout artefact crypto temporaire (DEK envelope, rekey, fragment, clé éphémère, capsule) est chiffré au repos (AES-256-GCM ou HSM envelope). Aucun secret en clair en base. Invariant crypto critique.
INV-287-17 Toute vérification de certificat DOIT utiliser un trust-store obligatoire (non optionnel). Évite validation faible.
INV-287-18 Toute primitive crypto du flux DOIT avoir des tests roundtrip complets. Prévention erreurs type double-hash.
INV-287-19 Purge proactive des artefacts temporaires au démarrage des flux et après état terminal. Zéro résiduel post-crash.
INV-287-20 Lock distribué, idempotence, réconciliation, rate-limit et clearing sont obligatoires. Robustesse distribuée.
INV-287-21 Écritures DB et journal d’audit append-only DOIVENT être synchrones et bloquantes ; seuls agrégats/ancrages peuvent être async post-commit selon §5.7. Cohérence fail-closed et atomicité.
INV-287-22 Les accès à la preuve via contexte partage DOIVENT être protégés par contrôle inter-module explicite. Isolation cross-module.
INV-287-23 Révocation non rétroactive : les accès passés restent immuables dans le journal. Exactitude probatoire.
INV-287-24 Mention UX explicite “révocation = accès futurs seulement” DOIT être affichée au propriétaire. Alignement attente utilisateur.
INV-287-25 Toute règle de cette spec est testable via §7/§8 ; sinon classée hors périmètre. Contractualisation vérifiable.

Note de traçabilité : - INV-287-14 est retiré du corpus des invariants non négociables. - L’export ZIP est conservé en fonctionnalité optionnelle documentée (Q-287-06).

5. Flux nominaux

5.1 Modèle de données contractuel (source unique des formats)

ID Donnée Format / encodage Taille Jeu de caractères Case Regex / contrainte Si invalide
D-287-01 proof_id UUID v4 texte 36 chars [0-9a-f-] insensitive ^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$ 400
D-287-02 owner_user_id UUID v4 texte 36 chars [0-9a-f-] insensitive idem D-287-01 401/403
D-287-03 recipient_email Email UTF-8 normalisé lower-case 3..254 chars ASCII printable email-set insensitive (normalisé) Ordre obligatoire: (1) vérifier longueur <=254, puis (2) regex ^(?=.{3,254}$)[A-Za-z0-9._%+-]{1,64}@[A-Za-z0-9.-]{1,189}\.[A-Za-z]{2,63}$ 400
D-287-04 share_link_id UUID v4 36 chars [0-9a-f-] insensitive idem D-287-01 rejet transaction
D-287-05 share_url_token base64url sans padding (256 bits) 43 chars [A-Za-z0-9_-] sensitive ^[A-Za-z0-9_-]{43}$ 404 générique
D-287-06 share_state Enum ASCII 6..18 chars [A-Z_] sensitive ^(PENDING_ACTIVATION|ACTIVE|OTP_BLOCKED|REVOKED|EXPIRED)$ 409
D-287-07 ttl_seconds Entier non signé 3..7 chars [0-9] n/a 900 <= ttl_seconds <= 2592000 400
D-287-08 created_at_utc RFC3339 UTC 20..30 chars ASCII sensitive datetime UTC parseable rejet transaction
D-287-09 expires_at_utc RFC3339 UTC 20..30 chars ASCII sensitive expires_at_utc = created_at_utc + ttl_seconds rejet transaction
D-287-10 revoked_at_utc RFC3339 UTC nullable 0 ou 20..30 chars ASCII sensitive null ou datetime UTC parseable rejet transaction
D-287-11 otp_code numérique décimal 6 chars [0-9] sensitive ^[0-9]{6}$ 404 générique (ou 429 si rate-limit)
D-287-12 otp_attempt_count entier non signé 1 char [0-9] n/a 0..5 clamp interdit, rejet
D-287-13 otp_blocked_until_utc RFC3339 UTC nullable 0 ou 20..30 chars ASCII sensitive null ou datetime UTC rejet transaction
D-287-14 recipient_session_id UUID v4 36 chars [0-9a-f-] insensitive idem D-287-01 404 générique
D-287-15 recipient_ephemeral_pubkey_pem PEM SPKI 160..4096 chars Base64 + headers PEM sensitive ^-----BEGIN PUBLIC KEY----- prefix obligatoire rejet transaction
D-287-16 recipient_ephemeral_cert_der_b64 DER encodé base64 256..8192 chars [A-Za-z0-9+/=] sensitive base64 valide rejet transaction
D-287-17 pre_rekey_envelope_b64 binaire chiffré encodé base64 64..16384 chars [A-Za-z0-9+/=] sensitive base64 valide, clair interdit rejet transaction
D-287-18 encrypted_dek_envelope_b64 binaire chiffré encodé base64 64..16384 chars [A-Za-z0-9+/=] sensitive base64 valide rejet transaction
D-287-19 proof_hash_sha256_hex SHA-256 hex 64 chars [a-f0-9] sensitive ^[a-f0-9]{64}$ 500 fail-closed
D-287-20 merkle_proof_canonical_json JSON canonique UTF-8 1..65536 bytes UTF-8 keys sensitive JSON parseable + ordre canonique stable 500 fail-closed
D-287-21 tsa_token_der_b64 DER base64 64..200000 chars [A-Za-z0-9+/=] sensitive base64 valide 500 fail-closed
D-287-22 blockchain_anchor_txid_hex hash hex 64 chars [a-f0-9] sensitive ^[a-f0-9]{64}$ 500 fail-closed
D-287-23 audit_event_type Enum ASCII 7..16 chars [A-Z_] sensitive ^(LINK_CREATED|LINK_ACTIVATED|LINK_VIEWED|LINK_EXPORTED|LINK_REVOKED|LINK_EXPIRED)$ rejet transaction
D-287-24 audit_event_hash_sha256_hex SHA-256 hex 64 chars [a-f0-9] sensitive ^[a-f0-9]{64}$ rejet transaction
D-287-25 idempotency_key_sha256_hex SHA-256 hex 64 chars [a-f0-9] sensitive ^[a-f0-9]{64}$ 409
D-287-26 export_manifest_jcs JSON canonique UTF-8 512..262144 bytes UTF-8 keys sensitive clés minimales: proof_hash,merkle,tsa,anchor,audit_subset,manifest_signature 500 fail-closed
D-287-27 export_zip_sha256_hex SHA-256 hex 64 chars [a-f0-9] sensitive ^[a-f0-9]{64}$ 500 fail-closed
D-287-28 client_ip IPv4/IPv6 texte 7..45 chars [0-9a-fA-F:.] insensitive IP parseable valeur non parseable non stockée
D-287-29 user_agent texte UTF-8 brut 0..512 chars UTF-8 printable sensitive longueur <= 512 tronquage interdit, rejet en stockage audit brut
D-287-30 recipient_capsule_encrypted_b64 capsule PRE chiffrée base64 64..16384 chars [A-Za-z0-9+/=] sensitive base64 valide 503 fail-closed
D-287-31 otp_failed_attempts_total entier non signé 1..3 chars [0-9] n/a 0..50 rejet transaction
D-287-32 audit_prev_event_hash_sha256_hex SHA-256 hex 64 chars [a-f0-9] sensitive ^[a-f0-9]{64}$ ou valeur genesis rejet transaction
D-287-33 audit_batch_merkle_root_sha256_hex SHA-256 hex 64 chars [a-f0-9] sensitive ^[a-f0-9]{64}$ rejet transaction
D-287-34 export_manifest_signature_b64 signature binaire base64 64..8192 chars [A-Za-z0-9+/=] sensitive signature vérifiable via cert plateforme inclus 503 fail-closed
D-287-35 export_platform_signing_cert_pem certificat PEM X.509 256..16384 chars PEM sensitive chaîne X.509 valide + racine trustée offline 503 fail-closed

Référencement unique : toutes les sections suivantes référencent D-287-* sans redéfinition de format.

5.2 Bornes numériques contractuelles

Charge nominale contractuelle pour les mesures P95 de cette story : - 1 lien actif, 10 RPS, environnement staging mono-instance.

Paramètre Défaut Min Max Unité Contexte de référence Percentile Hors bornes
link_ttl_seconds 604800 900 2592000 secondes Lien partage n/a 400
max_consultations_per_link (0=illimité) 0 0 1000 consultations Contrôle d’usage n/a 400
otp_code_digits 6 6 6 digits OTP email n/a rejet config
otp_max_attempts_per_session 5 5 5 tentatives Session destinataire n/a blocage 15 min
otp_failed_attempts_total_per_link_max 50 50 50 tentatives cumulées/lien Anti-bruteforce global lien n/a REVOKED + notification owner
otp_block_duration_seconds 900 900 900 secondes Anti-bruteforce OTP n/a rejet config
otp_blocks_before_owner_notification 3 3 3 blocages Sécurité destinataire n/a rejet config
otp_resend_limit_per_hour 3 1 3 renvois/heure/lien Anti-spam OTP n/a 429
session_inactivity_timeout_seconds 1800 300 7200 secondes Session destinataire n/a 400/rejet config
link_generation_latency_p95_ms 3000 0 5000 ms API backend partage (charge nominale) P95 alerte SLO + incident
revocation_effective_latency_p95_ms 2000 0 5000 ms Propagation révocation (charge nominale) P95 incident SLO critique
forced_session_termination_roundtrip_max 1 1 1 round-trip Session active après révocation n/a non conforme
create_links_per_proof_per_24h 50 1 500 liens/24h/preuve Quota abus n/a 429
create_links_per_owner_per_24h 500 10 5000 liens/24h/compte Quota abus n/a 429
active_links_per_owner_max 200 1 2000 liens actifs Limite stockage/surface n/a 429
distributed_lock_ttl_seconds 600 60 1800 secondes Concurrence multi-instance n/a rejet config
idempotency_window_hours 24 1 168 heures Retry-safe n/a rejet config
reconciliation_interval_seconds 300 60 900 secondes Worker réconciliation n/a rejet config
orphan_threshold_seconds 900 180 3600 secondes Détection orphelins n/a rejet config
open_requests_per_ip_per_min 30 1 120 req/min/IP Protection anti-scan n/a 429
clearing_success_cycles_required 2 1 10 cycles Retour état HEALTHY n/a rejet config
export_zip_max_size_mb 200 1 1024 MB Export composite (si activé) n/a 413
audit_anchor_batch_size_entries 100 10 1000 entrées Batch Merkle pour ancrage blockchain n/a rejet config

Justification sécurité : - otp_max_attempts_per_session=5 est fixé (non configurable MVP), justifié par INV-287-04 et la contrainte anti-bruteforce. - Les réponses 429 sont réservées aux conditions de rate-limit/anti-abus, sans fuite d’existence de ressource.

5.3 SLA temporels (transitions d’état)

SLA Défaut Min Max Configurabilité Comportement à expiration
sla_link_ttl 604800s 900s 2592000s Oui PENDING_ACTIVATION/ACTIVE/OTP_BLOCKED -> EXPIRED, accès révoqué
sla_session_inactivity 1800s 300s 7200s Oui session invalide, OTP requis pour nouvelle session
sla_otp_block_window 900s 900s 900s Non OTP_BLOCKED -> ACTIVE (si lien non terminal), compteur session reset
sla_revocation_effective 2000ms (P95) 0ms 5000ms Non (MVP) nouvelles requêtes refusées, session forcée au prochain round-trip
sla_orphan_reconciliation 900s 180s 3600s Oui rattrapage asynchrone + journal RECONCILED
sla_temp_artifact_purge 300s 0s 900s Oui purge des artefacts crypto temporaires après REVOKED/EXPIRED
sla_audit_anchor_delay 300s 60s 900s Oui dépassement => alerte + réconciliation d’ancrage

Aucune autre transition temporelle identifiée.

5.4 Flux nominaux contractuels

  1. F-287-01 — Création du lien
  2. Entrées valides: D-287-01, D-287-03, D-287-07, D-287-25.
  3. Contrôles: ownership preuve, quotas, bornes TTL.
  4. Sorties contractuelles: D-287-04, D-287-05, D-287-06=PENDING_ACTIVATION, D-287-09, artefacts D-287-15..D-287-18.
  5. Journalisation obligatoire D-287-23=LINK_CREATED (append synchrone, hash-chain).

  6. F-287-02 — Activation destinataire OTP

  7. Ouverture lien: envoi OTP au D-287-03.
  8. Vérification OTP: D-287-11, compteur session D-287-12, blocage via D-287-13 si seuil session atteint.
  9. Compteur cumulé par lien D-287-31: au seuil 50, transition définitive vers REVOKED, notification propriétaire.
  10. Succès: transition PENDING_ACTIVATION|OTP_BLOCKED -> ACTIVE, session D-287-14.
  11. Journalisation obligatoire LINK_ACTIVATED en succès, LINK_REVOKED en verrouillage définitif anti-abus.
  12. Réponses destinataire: 404 générique (cas invalides), 429 uniquement pour rate-limit/anti-abus.

  13. F-287-03 — Consultation de la preuve

  14. Préconditions: état ACTIVE, session valide.
  15. Données restituées: document chiffré + D-287-30, D-287-19, D-287-20, D-287-21, D-287-22.
  16. Aucun contenu clair serveur exposé.
  17. Journalisation obligatoire LINK_VIEWED, synchrone et bloquante avant réponse.

  18. F-287-04 — Export composite autonome (optionnel, nice-to-have)

  19. Préconditions: état ACTIVE, session valide, feature export_zip_enabled=true.
  20. Production ZIP: manifeste D-287-26, signature plateforme D-287-34, certificat plateforme D-287-35, hash ZIP D-287-27, artefacts probatoires + sous-ensemble de journal lié au lien.
  21. Vérification offline: hash + signature manifeste vérifiables sans API ProbatioVault.
  22. Journalisation obligatoire LINK_EXPORTED.
  23. Si export_zip_enabled=false: endpoint destinataire retourne 404 générique (pas de différenciation de capability).

  24. F-287-05 — Révocation propriétaire

  25. Préconditions: owner authentifié.
  26. Transitions autorisées: PENDING_ACTIVATION|ACTIVE|OTP_BLOCKED -> REVOKED.
  27. Effets: invalidation sessions immédiate, interdiction accès futurs.
  28. Journalisation obligatoire LINK_REVOKED.

  29. F-287-06 — Expiration TTL

  30. Déclenchement: now_utc >= D-287-09.
  31. Transitions autorisées: PENDING_ACTIVATION|ACTIVE|OTP_BLOCKED -> EXPIRED.
  32. Effets: invalidation sessions, invalidation droits PRE, purge artefacts temporaires.
  33. Journalisation obligatoire LINK_EXPIRED.

  34. F-287-07 — Consultation propriétaire des liens/accès

  35. Le propriétaire DOIT pouvoir lister ses liens actifs et événements par lien.
  36. Aucune agrégation transverse inter-propriétaires sur destinataires.
  37. Données minimisées RGPD (pas de profil global destinataire).
  38. Avertissements UI: “pas de DRM” (affiché une seule fois puis mémorisé) et “révocation = accès futurs seulement”.

5.5 Machine d’états (transitions retour explicites)

États: PENDING_ACTIVATION, ACTIVE, OTP_BLOCKED, REVOKED (terminal), EXPIRED (terminal).

État PENDING_ACTIVATION

Transition sortante Statut Condition Comportement
-> ACTIVE AUTORISÉE OTP valide Session créée
-> OTP_BLOCKED AUTORISÉE 5 OTP invalides/session Blocage 15 min
-> REVOKED AUTORISÉE Révocation owner OU D-287-31>=50 Terminal, notification owner si anti-abus
-> EXPIRED AUTORISÉE TTL dépassé Droits invalidés
-> PENDING_ACTIVATION INTERDITE (transition d’état) no-op Aucun changement d’état

État ACTIVE

Transition sortante Statut Condition Comportement
-> OTP_BLOCKED AUTORISÉE Échecs OTP réauth/session Blocage 15 min
-> REVOKED AUTORISÉE Révocation owner OU D-287-31>=50 Accès futurs interdits
-> EXPIRED AUTORISÉE TTL dépassé Accès futurs interdits
-> PENDING_ACTIVATION INTERDITE Retour en arrière Rejet
-> ACTIVE INTERDITE (transition d’état) no-op Aucun changement d’état

État OTP_BLOCKED

Transition sortante Statut Condition Comportement
-> ACTIVE AUTORISÉE cooldown expiré + OTP valide Reprise contrôlée
-> REVOKED AUTORISÉE Révocation owner OU D-287-31>=50 Terminal, notification owner si anti-abus
-> EXPIRED AUTORISÉE TTL dépassé Terminal
-> PENDING_ACTIVATION INTERDITE Retour initial Rejet
-> OTP_BLOCKED INTERDITE (transition d’état) no-op Aucun changement d’état

État REVOKED (terminal)

Transition sortante Statut Condition Comportement
-> * INTERDITE état terminal état terminal, résolution manuelle uniquement

État EXPIRED (terminal)

Transition sortante Statut Condition Comportement
-> * INTERDITE état terminal état terminal, résolution manuelle uniquement

Comportement downgrade/retour : - ACTIVE -> OTP_BLOCKED: accès suspendu, données existantes conservées, quotas/rate-limits maintenus. - OTP_BLOCKED -> ACTIVE: accès restauré après contrainte temporelle et OTP valide. - PENDING_ACTIVATION|ACTIVE|OTP_BLOCKED -> REVOKED|EXPIRED: fonctionnalités verrouillées immédiatement, sessions invalidées, artefacts temporaires purgés, journal conservé. - PENDING_ACTIVATION|ACTIVE|OTP_BLOCKED -> REVOKED via seuil OTP cumulé (D-287-31>=50): verrouillage définitif anti-abus + notification propriétaire. - Toute transition inverse non listée est interdite.

Checklist machine à états : - [x] Chaque état liste ses transitions sortantes (autorisées/interdites). - [x] États terminaux explicitent -> * : INTERDITE. - [x] Invariants dédiés couvrent le modèle (INV-287-06 à INV-287-10).

5.6 Mécanismes de protection distribuée

Mécanisme Contrat
Lock distribué Scope share_link_id (activation/consult/revoke/export) + proof_id (compteurs quota), TTL 600s, lock non acquis => 409 (SHARE_BUSY)
Idempotence Clé D-287-25, fenêtre 24h, même clé + même payload => même résultat, même clé + payload différent => 409 (IDEMPOTENCY_CONFLICT), payload rejoué au-delà de la fenêtre => 409 (IDEMPOTENCY_WINDOW_EXPIRED)
Réconciliation Cron 300s, orphelin si activité > 900s sur état non terminal, rattrapage journal/ancrage/session
Rate-limiting Granularités IP, lien, preuve, compte; quotas §5.2 ; dépassement => 429 + Retry-After (sans fuite d’existence lien)
Clearing Retour HEALTHY après 2 cycles conformes successifs de réconciliation

Checklist protection distribuée : - [x] Lock: scope + TTL + comportement lock non acquis. - [x] Idempotence: clé + durée de déduplication + garde-fou hors fenêtre. - [x] Réconciliation: intervalle + seuil orphelin + rattrapage. - [x] Rate-limit: granularité + quota + dépassement. - [x] Clearing: cycles conformes + comptage.

5.7 Atomicité multi-composant (DB + audit synchrone + async non bloquant)

Scope Synchrone/Async Garantie
INSERT/UPDATE états partage + session + métadonnées Synchrone (transaction) Atomicité ACID
Journal append-only + hash-chain (D-287-24,D-287-32) Synchrone bloquant (avant réponse) Fail-closed: si échec audit, réponse 503 et rollback/unité logique non validée
Agrégation/ancrage intégrité journal (D-287-33) Async post-commit Rattrapage par réconciliation
Crash pré-commit Rollback total, aucun artefact persistant
Crash post-commit avant ancrage DB + audit cohérents; ancrage rattrapé sans perte d’ordre

5.8 Contraintes inter-modules

Élément Contrat
Routes d’autre module à protéger GET /proofs/:id/document, GET /proofs/:id/probative-metadata, POST /proofs/:id/export-composite (contexte destinataire)
Mécanisme de protection Guard partage vérifiant share_state=ACTIVE, session valide, ownership indirect via lien
Données cross-schema nécessaires vault_secure.share_links.proof_id, vault_secure.proofs.id, vault_secure.proofs.owner_user_id
Résolution FK cross-module share_links.proof_id -> proofs.id
Scope d’enregistrement Guard appliqué au périmètre partage/preuve exposé destinataire, pas global plateforme
Exceptions d’accès Aucune exception de rôle pour contourner révocation/expiration

5.9 Stratégie de migration DDL (colonne existante)

Aucune modification de colonne existante n’est identifiée dans le besoin PD-287.

Élément cluster migration Statut
Type actuel -> cible colonne existante Non applicable
Backfill Non applicable
Impact triggers existants Non applicable
Impact workers/services dépendants (changement type colonne) Non applicable
Down migration colonne modifiée Non applicable
Contraintes ajoutées sur colonne existante Non applicable

5.10 Invariant crypto additionnel

  • INV-287-16 est obligatoire sur tout artefact PRE/clé/certificat/session/capsule secret.
  • Secret en clair en base: interdit.
  • Persistance chiffrée: obligatoire.

5.11 Politique de rétention

Domaine Données concernées Déclencheur de rétention Durée Action à échéance
Données destinataire recipient_email, client_ip, user_agent, recipient_session_id, recipient_ephemeral_*, compteurs OTP (D-287-12,D-287-31) terminal_at_utc = revoked_at_utc sinon expires_at_utc 90 jours après terminal_at_utc suppression irréversible (ou anonymisation irréversible pour champs requis stats)
Artefacts temporaires crypto envelopes, fragments, capsules temporaires état terminal / démarrage service immédiat selon sla_temp_artifact_purge purge technique
Journal d’audit événements append-only liés au lien cycle de vie preuve originale même durée que la preuve originale conservation intégrale, immuabilité maintenue

Règles d’exécution: - Job de purge planifié quotidien + purge opportuniste en entrée d’état terminal. - Toute purge DOIT être traçable par événement technique non destructif (métadonnée de purge), sans supprimer l’historique d’audit probatoire. - Cette section rend INV-287-15 testable contractuellement.

5bis. Diagrammes (si applicable)

Diagramme d’état (>= 3 états)

stateDiagram-v2
    [*] --> PENDING_ACTIVATION

    PENDING_ACTIVATION --> ACTIVE: OTP valide
    PENDING_ACTIVATION --> OTP_BLOCKED: 5 OTP invalides/session
    PENDING_ACTIVATION --> REVOKED: revoke owner ou seuil OTP cumule atteint
    PENDING_ACTIVATION --> EXPIRED: TTL atteint

    ACTIVE --> OTP_BLOCKED: echec OTP reauth
    ACTIVE --> REVOKED: revoke owner ou seuil OTP cumule atteint
    ACTIVE --> EXPIRED: TTL atteint

    OTP_BLOCKED --> ACTIVE: cooldown expire + OTP valide
    OTP_BLOCKED --> REVOKED: revoke owner ou seuil OTP cumule atteint
    OTP_BLOCKED --> EXPIRED: TTL atteint

    note right of PENDING_ACTIVATION
      Transitions no-op interdites
      (ex: PENDING_ACTIVATION->PENDING_ACTIVATION)
    end note

    note right of ACTIVE
      Retour arriere vers PENDING_ACTIVATION interdit
    end note

    note right of OTP_BLOCKED
      No-op OTP_BLOCKED->OTP_BLOCKED interdit
    end note

    note right of REVOKED
      Etat terminal: aucune transition sortante
    end note

    note right of EXPIRED
      Etat terminal: aucune transition sortante
    end note

Diagramme de séquence (flow multi-services + crypto)

sequenceDiagram
    participant O as OwnerClient
    participant S as SharingAPI
    participant P as ProofService
    participant C as CryptoService
    participant M as OTP-Mail
    participant A as AuditJournal
    participant R as RecipientClient

    O->>S: POST /shares {proof_id, recipient_email, ttl_seconds, idempotency_key}
    S->>P: get_encrypted_proof(proof_id)
    P-->>S: {encrypted_dek_envelope_b64, encrypted_doc_ref, proof_hash_sha256_hex, merkle_proof_json, tsa_token_der_b64, anchor_txid_hex}
    S->>C: PRE_ReKeyGen(owner_key_ref, recipient_ephemeral_pubkey_pem)
    C-->>S: pre_rekey_envelope_b64
    S->>S: token_raw(32 bytes) -> base64url(43 chars)
    S->>A: append_sync(event=LINK_CREATED, prev_hash)
    S-->>O: 201 {share_url_token, state=PENDING_ACTIVATION, expires_at_utc}

    R->>S: GET /sharing/{share_url_token}
    S->>M: send_otp(recipient_email, otp_context=share_link_id)
    M-->>R: otp_code(6 digits)

    R->>S: POST /sharing/{token}/activate {otp_code}
    S->>S: verify_otp_session_and_total()
    alt otp_ok
        S->>A: append_sync(event=LINK_ACTIVATED, prev_hash)
        S-->>R: 200 {recipient_session_id, state=ACTIVE}
    else otp_rate_limited
        S-->>R: 429 generic
    else otp_invalid_or_link_invalid
        S-->>R: 404 generic
    end

    R->>S: GET /sharing/{token}/proof {recipient_session_id}
    alt session_inactive
        S-->>R: 404 generic
        R->>S: POST /sharing/{token}/reauth/request-otp
        S->>M: send_otp(recipient_email, reauth_context)
        R->>S: POST /sharing/{token}/reauth/confirm {otp_code}
        S-->>R: 200 {recipient_session_id, state=ACTIVE}
    else session_active
        S->>C: PRE_ReEncrypt(encrypted_dek_envelope_b64, pre_rekey_envelope_b64)
        C-->>S: recipient_capsule_encrypted_b64
        S->>A: append_sync(event=LINK_VIEWED, prev_hash)
        S-->>R: {encrypted_doc_ref, recipient_capsule_encrypted_b64, proof_hash_sha256_hex, merkle_proof_json, tsa_token_der_b64, anchor_txid_hex}
    end

    opt export_zip_enabled=true
        R->>S: POST /sharing/{token}/export
        S->>S: build_zip(document + proof_artifacts + jcs_manifest + manifest_signature + platform_cert)
        S->>A: append_sync(event=LINK_EXPORTED, prev_hash)
        S-->>R: export_zip + export_zip_sha256_hex
    end

    O->>S: POST /shares/{share_link_id}/revoke
    S->>A: append_sync(event=LINK_REVOKED, prev_hash)
    S-->>O: 204

    R->>S: GET /sharing/{token}/proof
    S-->>R: 404 generic

6. Cas d’erreur

ID Cas Réponse attendue
ERR-287-01 proof_id invalide 400
ERR-287-02 recipient_email invalide 400
ERR-287-03 ttl_seconds hors bornes 400
ERR-287-04 Quota création dépassé (preuve/compte/période) 429
ERR-287-05 Token/lien introuvable, invalide, expiré, révoqué, non autorisé (destinataire) 404 générique non différenciant
ERR-287-06 OTP invalide (tentatives restantes > 0) 404 générique
ERR-287-07 5 échecs OTP session atteints 429 + share_state=OTP_BLOCKED
ERR-287-08 Renvoi OTP > limite horaire 429
ERR-287-09 Session invalide ou expirée par inactivité 404 générique + réauth OTP requise côté UX
ERR-287-10 Lien révoqué en session active requête suivante refusée (404 générique)
ERR-287-11 Lien expiré en session active requête suivante refusée (404 générique)
ERR-287-12 Lock distribué non acquis 409 (SHARE_BUSY)
ERR-287-13 Collision idempotence (même clé, payload différent) 409 (IDEMPOTENCY_CONFLICT)
ERR-287-14 Échec journal append-only synchrone 503 fail-closed (pas de contenu probatoire servi)
ERR-287-15 Échec PRE/crypto 503 fail-closed
ERR-287-16 Trust-store absent/incomplet/invalide pour vérification requise 503 fail-closed
ERR-287-17 ZIP export > taille max 413
ERR-287-18 Tentative de re-partage par destinataire 403
ERR-287-19 Replay idempotent au-delà de la fenêtre contractuelle 409 (IDEMPOTENCY_WINDOW_EXPIRED)
ERR-287-20 Plafond cumulé OTP/lien atteint (D-287-31=50) 429 + transition REVOKED + notification propriétaire

7. Critères d’acceptation (testables)

ID Critère Observable
CA-287-01 TTL borné à 15 min..30 jours, défaut 7 jours création hors bornes rejetée, valeur par défaut appliquée
CA-287-02 État initial = PENDING_ACTIVATION enregistrement lien créé
CA-287-03 OTP requis à chaque nouvelle session nouvelle session sans OTP refusée
CA-287-04 5 échecs OTP/session => blocage 15 min transition vers OTP_BLOCKED + timestamp blocage
CA-287-05 3 blocages OTP => notification propriétaire événement notification tracé
CA-287-06 Renvoi OTP limité à 3/h/lien 4e tentative rejetée 429
CA-287-07 Activation OTP valide => ACTIVE transition et audit LINK_ACTIVATED
CA-287-08 Révocation effective <= 2s P95 (charge nominale: 1 lien actif, 10 RPS, staging mono-instance) métriques de propagation
CA-287-09 Expiration TTL coupe tout accès futur requêtes post-expiration refusées
CA-287-10 REVOKED et EXPIRED n’ont aucune sortie autorisée tentative de transition rejetée
CA-287-11 Lien seul sans OTP/session ne donne pas accès consultation refusée
CA-287-12 Consultation fournit document chiffré + capsule PRE + hash + Merkle + TSA + ancrage payload complet conforme
CA-287-13 Si l’export ZIP est activé, ZIP vérifiable hors plateforme avec signature manifeste ProbatioVault vérifiable offline via certificat inclus vérification locale réussie sans API ProbatioVault
CA-287-14 Aucun endpoint/service/log/dump mémoire du périmètre story n’expose le document en clair (audit réseau + grep mémoire + contrôle logs/dumps) preuves techniques de contrôle borné
CA-287-15 Original WORM inchangé après partage/révocation/expiration hash original identique avant/après
CA-287-16 Journal append-only contient les événements contractuels, chaînés (prev_hash) et ancrés périodiquement par Merkle root présence/ordre/immuabilité vérifiés
CA-287-17 Révocation non rétroactive événements passés restent présents après révocation
CA-287-18 Anti-enumeration destinataire: 404 générique homogène pour token/lien invalide/expiré/révoqué/inexistant ; 429 réservé au rate-limit enveloppe/codes/messages conformes
CA-287-19 Identité éphémère distincte par lien deux liens même email => clés/certs différents
CA-287-20 Re-partage impossible aucune route/action valide destinataire pour partager
CA-287-21 Quotas proof/account/période appliqués dépassement renvoie 429
CA-287-22 Lock distribué empêche conflits concurrents un seul traitement concurrent par scope
CA-287-23 Idempotence retry-safe sur création/révocation/export + payload hors fenêtre idempotency rejeté 409 replay conforme + conflit contrôlé
CA-287-24 Réconciliation rattrape orphelins <= seuil état cohérent après cycle worker
CA-287-25 Purge proactive des artefacts temporaires exécutée artefacts absents après terminal + au démarrage
CA-287-26 Warning “pas de DRM” affiché une seule fois au propriétaire puis mémorisé trace d’affichage initial + non-réaffichage
CA-287-27 Information RGPD destinataire affichée avant activation capture UI + trace consentement info
CA-287-28 Tests roundtrip crypto présents pour chaque primitive du flux suite tests crypto verte
CA-287-29 La vérification de certificat éphémère destinataire échoue si trust-store absent ou incomplet rejet 503 fail-closed
CA-287-30 Politique de rétention appliquée: suppression données destinataire à J+90 post-terminal, audit conservé tant que preuve originale traces de purge et conservation conformes
CA-287-31 Plafond cumulé OTP par lien appliqué: 50 tentatives invalides cumulées => REVOKED définitif + notification propriétaire état final + événement notification

8. Scénarios de test (Given / When / Then)

  1. ST-287-01 Création nominale
  2. Given propriétaire authentifié, preuve possédée, quotas OK
  3. When création lien avec TTL par défaut
  4. Then lien créé en PENDING_ACTIVATION, expiration à J+7, audit LINK_CREATED

  5. ST-287-02 TTL hors borne

  6. Given propriétaire authentifié
  7. When création lien avec ttl_seconds=600
  8. Then 400, aucun lien créé

  9. ST-287-03 Quota dépassé

  10. Given quota preuve atteint sur 24h
  11. When nouvelle création de lien
  12. Then 429

  13. ST-287-04 Activation OTP réussie

  14. Given lien PENDING_ACTIVATION valide
  15. When OTP correct soumis
  16. Then état ACTIVE, session créée, audit LINK_ACTIVATED

  17. ST-287-05 OTP échec puis blocage

  18. Given lien PENDING_ACTIVATION
  19. When 5 OTP erronés sur une session
  20. Then état OTP_BLOCKED pendant 15 min

  21. ST-287-06 Déblocage OTP

  22. Given lien OTP_BLOCKED
  23. When 15 min écoulées puis OTP correct
  24. Then état ACTIVE

  25. ST-287-07 Consultation sans OTP/session

  26. Given token lien valide
  27. When appel direct consultation sans session valide
  28. Then accès refusé en 404 générique

  29. ST-287-08 Consultation nominale

  30. Given lien ACTIVE + session valide
  31. When consultation preuve
  32. Then payload chiffré + recipient_capsule_encrypted_b64 + hash/Merkle/TSA/ancrage + audit LINK_VIEWED

  33. ST-287-09 Révocation en session active

  34. Given lien ACTIVE et destinataire connecté
  35. When propriétaire révoque
  36. Then requête suivante destinataire refusée (au plus un round-trip), dans SLA P95 2s

  37. ST-287-10 Expiration automatique

  38. Given lien ACTIVE
  39. When now_utc >= expires_at_utc
  40. Then transition EXPIRED, accès futurs refusés (404 générique), audit LINK_EXPIRED

  41. ST-287-11 Avertissements propriétaire

  42. Given propriétaire sur écran de gestion partage
  43. When premier affichage puis retour écran
  44. Then “pas de DRM” affiché une fois puis mémorisé ; “révocation = accès futurs seulement” affiché

  45. ST-287-12 Anti-enumeration

  46. Given tokens inconnu, expiré, révoqué, invalide
  47. When appel consultation sur chacun
  48. Then même enveloppe 404 observable ; 429 uniquement sur dépassement rate-limit

  49. ST-287-13 Isolation inter-liens (modèle d’attaque explicite)

  50. Given deux liens vers même preuve et destinataires différents
  51. When fuite d’une clé éphémère du lien A
  52. Then seul le lien A est compromis ; le lien B reste intact/exploitable indépendamment

  53. ST-287-14 Journal fail-closed synchrone

  54. Given indisponibilité append-only
  55. When consultation demandée
  56. Then requête rejetée 503, aucun contenu probatoire servi, aucune validation partielle

  57. ST-287-15 Réconciliation orphelin

  58. Given crash post-commit avant ancrage batch Merkle
  59. When worker de réconciliation s’exécute
  60. Then ancrage complété, état final cohérent

  61. ST-287-16 Non-rétroactivité révocation

  62. Given un accès LINK_VIEWED déjà enregistré
  63. When révocation ensuite
  64. Then entrée historique inchangée, immuable

  65. ST-287-17 Re-partage interdit

  66. Given destinataire authentifié
  67. When tentative de création d’un nouveau lien
  68. Then 403

  69. ST-287-18 Plafond OTP cumulé

  70. Given lien enchaînant des sessions OTP invalides
  71. When otp_failed_attempts_total atteint 50
  72. Then transition définitive REVOKED, notification propriétaire, accès futurs refusés

  73. ST-287-19 Trust-store requis

  74. Given trust-store absent/incomplet
  75. When vérification certificat éphémère est requise
  76. Then 503 fail-closed

  77. ST-287-20 Rétention

  78. Given lien terminal (REVOKED ou EXPIRED) depuis >90 jours
  79. When job de purge est exécuté
  80. Then données destinataire supprimées ; journal d’audit conservé si preuve originale conservée

  81. ST-287-21 Idempotence hors fenêtre

  82. Given requête avec clé idempotence expirée
  83. When replay avec payload identique au-delà de la fenêtre
  84. Then 409 IDEMPOTENCY_WINDOW_EXPIRED

9. Hypothèses explicites

ID Hypothèse Impact si faux
H-287-01 session_inactivity_timeout_seconds retenu à 1800s (30 min) pour MVP, dans borne max 7200s Recalibrage SLA + tests OTP/session
H-287-02 Quotas initiaux retenus: 50/proof/24h, 500/owner/24h, 200 liens actifs/owner Ajustement rate-limit et tests de charge
H-287-03 SLO révocation P95 fixé à 2000ms (borne max 5000ms) Ajustement monitoring, UX et contrat INV-287-08
H-287-04 L’export, si activé, inclut le sous-ensemble de journal strictement lié au lien (pas historique global) Risque de sur/sous-exposition si stratégie différente
H-287-05 Domaine/sous-domaine anti-phishing dédié est disponible au déploiement Mesures anti-phishing incomplètes sans ce prérequis
H-287-06 Ancrage du journal asynchrone acceptable avec défaut 300s et délai max 900s Si non accepté, resserrer SLA et architecture ancrage
H-287-08 Vérification juridictionnelle finale en tribunal reste hors périmètre technique testable Ne pas interpréter la spec comme garantie de décision judiciaire

10. Contraintes techniques et points à clarifier

10.1 Contraintes techniques (stack projet cible)

Projet Stack réelle contractuelle Indices vérifiables
ProbatioVault-backend (cible principale PD-287) NestJS + TypeORM + PostgreSQL package.json, nest-cli.json
ProbatioVault-app (consommateur propriétaire) React Native + Expo SDK 54 + TypeScript package.json, app.json, metro.config.js
ProbatioVault-site (portail destinataire si web) Next.js + TypeScript next.config.js

Contraintes de cadrage: - La présente story cible le backend. - Aucune hypothèse Swift/SwiftUI. - Aucune hypothèse Spring Boot. - CT-287-01 (ex CA-287-29): conformité stack backend obligatoire et vérifiable sur artefacts.

10.2 Points à clarifier

ID Point à clarifier Donnée manquante / décision attendue
Q-287-01 Validation PO du timeout d’inactivité session (30 min proposé) Confirmer valeur finale
Q-287-02 Validation PO des quotas numériques initiaux Confirmer ou ajuster valeurs §5.2
Q-287-03 Validation SLO perf (link_generation/revocation) Confirmer bornes P95 et protocole de mesure
Q-287-05 Schéma canonique final du manifeste d’export offline signé Confirmer clés minimales, algorithme signature et versionnement
Q-287-06 Activation MVP de l’export ZIP (nice-to-have) Décision GO/NO-GO au lancement MVP
Q-287-07 Injection learnings contextuels ({{LEARNINGS}}) absente Fournir top-k learnings pour amendement éventuel (point processus)

Références

  • Epic : sharing (référence épique détaillée non fournie dans l’entrée)
  • JIRA : PD-287
  • Repos concernés : ProbatioVault-backend (principal), ProbatioVault-app (owner UI), ProbatioVault-site (portail destinataire), ProbatioVault-doc (référentiel normatif)
  • Documents associés : Expression de besoin PD-287 (2026-04-19), contraintes PO Q1..Q20, arbitrages PO ARB-1..ARB-10 ```

PD-287-tests.md ```markdown