Aller au contenu

PD-278 — Agent Report: dip-service

Agent: agent-developer Module: dip-service Story: PD-278 — NF Z42-013: ajout contractuel de l'etat DIP Date: 2026-03-01


1. Fichiers modifies

Fichier Action Justification
src/modules/documents/services/dissemination.service.ts Cree Service core metier DIP — orchestration SEALED<->DIP

2. Resume de l'implementation

DisseminationService

Service NestJS injectable implementant les deux flux metier de la communication DIP :

Flux F1 — disseminate(documentIds, actorId, motifCommunication) : SEALED -> DIP - Transaction ACID via QueryRunner explicite - SET LOCAL pour contexte RLS (INV-278-07) - SELECT FOR UPDATE ordonne par document_id ASC (LOCK-ORDER SS5.9, INV-278-12) - Gardes sequentielles par document (INV-278-02, INV-278-11) : - Etat = SEALED (E-409-STATE) - copies >= MIN_COPIES (E-422-GUARD-COPIES) - retention_due = false (E-409-RETENTION-DUE, INV-278-14) - UPDATE atomique: status=DIP, disseminated_at=NOW(), disseminated_by, package_id, motif - INSERT attestation dans vault_secure.dissemination_attestations (INV-278-05) - INSERT journal entry dans vault_integrity.integrity_journal_entries (INV-278-04, PD-251) - Post-commit: auditLogService.logAsync() (publication externe non-bloquante)

Flux F2 — returnFromDissemination(documentId, actorId) : DIP -> SEALED - Transaction ACID via QueryRunner - SET LOCAL pour contexte RLS - SELECT FOR UPDATE (LOCK-ORDER SS5.9) - Garde etat = DIP (E-409-STATE) - UPDATE atomique: status=SEALED, dissemination_returned_at=GREATEST(NOW(), disseminated_at) (INV-278-13) - Verification temporelle belt-and-suspenders (E-409-TEMPORAL-ORDER) - INSERT journal entry (INV-278-04) - Post-commit: auditLogService.logAsync()


3. Couverture des invariants

Invariant Mecanisme Localisation
INV-278-02 6 gardes sequentielles (etat, copies, retention + auth/role/RLS via guards/SET LOCAL) disseminate() lignes 112-178
INV-278-04 Journal entry INSERT synchrone dans meme tx que UPDATE status ; audit async post-commit disseminate() lignes 251-268, 277-289 ; returnFromDissemination() lignes 405-416, 422-432
INV-278-05 1 attestation par requete, INSERT dans vault_secure.dissemination_attestations dans meme tx disseminate() lignes 208-249
INV-278-06 Aucune mutation contenu en DIP (WORM). motif_communication via COALESCE (preservation existant) disseminate() UPDATE SQL ligne 195
INV-278-11 Package atomique — boucle sequentielle, 1 echec = ROLLBACK global (throw dans la boucle) disseminate() lignes 144-178
INV-278-12 SELECT FOR UPDATE ordonne par document_id ASC (// LOCK-ORDER: document_id ASC SS5.9) disseminate() lignes 115-130 ; returnFromDissemination() lignes 345-354
INV-278-13 returned_at = GREATEST(NOW(), disseminated_at) en SQL + check applicatif returnFromDissemination() lignes 378-403
INV-278-14 Garde retention_due=false sur SEALED->DIP ; retention_service BYPASSRLS pour cloture DIP->SEALED disseminate() lignes 170-177 ; SET LOCAL pour RLS

4. Couverture des forbidden

Interdit Conformite Justification
Promise.all sur les gardes Conforme Boucle for (const row of rows) sequentielle
Mutation du contenu documentaire en DIP Conforme Aucune mutation de encrypted_metadata, file_hash, ovh_path
Attestation ou audit hors transaction ACID Conforme INSERT attestation et journal dans meme queryRunner.tx
Timeout implicite DIP -> SEALED Conforme Pas de scheduler/cron — retour explicite uniquement
Math.random() pour identifiants Conforme randomUUID() de node:crypto pour package_id, attestation_id, request_id
Cache applicatif pour retention_due Conforme Lecture directe DB via SELECT FOR UPDATE

5. Decisions architecturales

architectural_decisions:
  - decision: "Journal entry comme garantie transactionnelle (pattern outbox)"
    rationale: "Le codebase existant (RestitutionService) utilise appendJournalEntryInTransaction pour l'atomicite. La table audit_outbox n'existe pas  le journal integrity est l'outbox. auditLogService.logAsync() gere la publication post-commit."
    alternatives_considered: ["Table audit_outbox dediee", "INSERT direct dans vault_secure.audit_log en tx"]
    trade_offs: ["Alignement avec pattern existant PD-279. Le journal entry est la preuve transactionnelle, l'audit log est la publication externe."]
  - decision: "COALESCE(motif_communication, $4) dans l'UPDATE SQL"
    rationale: "Preserve la valeur existante si non-NULL (WORM INV-278-06). Seule la premiere ecriture persiste le motif. Le trigger DB trg_motif_communication_worm est la derniere ligne de defense."
    alternatives_considered: ["SET motif_communication = $4 direct", "Verification applicative avant UPDATE"]
    trade_offs: ["COALESCE = idempotent en SQL, mais necessite que le trigger bloque les cas edge"]

6. Dependances inter-agents

Dependance Agent responsable Statut Impact
JournalEventType.DIP_DISSEMINATED / DIP_RETURNED agent-journal (C7) Non encore present dans enum Service compile quand C7 est livre
DisseminationAttestation entity agent-entity (C2) Non encore present Table vault_secure.dissemination_attestations doit exister (migration C1)
DocumentStateMachineService SEALED<->DIP transitions agent-state-machine (C3) Non encore present dans ALLOWED_TRANSITIONS Service ne depend pas directement de la state machine (gardes internes)
DisseminationConfig agent-config (C4) Present et complet Config validee Joi, registerAs('dissemination')
DTOs (CreateDisseminationDto, error codes) agent-dto (C5) Present et complet DisseminationError, DisseminationErrorCode utilises
AuditActionType.DOCUMENT_DISSEMINATED/RETURNED/DENIED agent-audit (C6) Present et complet 3 types disponibles

7. Hypotheses documentees

ID Hypothese Justification
H-DIP-SVC-01 Le pattern outbox est le journal integrity (vault_integrity.integrity_journal_entries), pas une table audit_outbox dediee Le codebase existant (RestitutionService) utilise ce pattern. Aucune table audit_outbox n'existe.
H-DIP-SVC-02 SET LOCAL app.current_user_id est suffisant pour le contexte RLS DIP Pattern identique a RestitutionService. Le retention_service utilise BYPASSRLS au niveau PostgreSQL (REX PD-16).
H-DIP-SVC-03 Les UUID dans documents[] sont deja normalises lowercase par ParseUUIDPipe du controller Le DTO utilise @IsUUID('4') et le controller ParseUUIDPipe.
H-DIP-SVC-04 La colonne geo_copy_count est maintenue par la couche stockage (PD-279) Phase 0 du plan — validation H-06. La colonne existe avec DEFAULT 0 (fail-safe).

8. Points de vigilance

ID Point Mitigation
V-SVC-01 L'enum JournalEventType n'a pas encore DIP_DISSEMINATED/DIP_RETURNED Dependance C7 (agent-journal). Compilation echouera tant que C7 n'est pas livre.
V-SVC-02 La table vault_secure.dissemination_attestations n'existe pas encore Dependance C1 (agent-migration). INSERT echouera a runtime sans la migration.
V-SVC-03 signature_ref est NULL (STUB PD-37) Le service inserere NULL pour signature_ref. Quand PD-37 sera livre, ajouter l'appel HSM ici.
V-SVC-04 Deadlock possible si un autre module verrouille les documents dans un ordre different Convention LOCK-ORDER document_id ASC (SS5.9) documentee. Commentaire dans le code.
V-SVC-05 Fenetre de race retention_due (5 min max reconciliation) La garde retention_due = false est lue dans le SELECT FOR UPDATE — source de verite DB.

9. Matrice de couverture tests

Les tests contractuels suivants sont couverts par l'implementation du service :

Test ID Couvert par Mecanisme dans le service
TC-NOM-01 disseminate() Gardes OK -> UPDATE + attestation + journal
TC-NOM-02 disseminate() Package 1 et 100 docs, SELECT FOR UPDATE ordonne
TC-NOM-03 returnFromDissemination() Retour explicite, GREATEST(NOW, disseminated_at)
TC-NOM-04 (absence de scheduler) Pas de cron/timer dans le service
TC-NOM-06 disseminate() motif persiste + present attestation + journal
TC-NOM-07 disseminate() Package_id NULL mono / UUID v4 multi
TC-ERR-06 disseminate() garde etat Rejet E-409-STATE
TC-ERR-07 disseminate() garde copies Rejet E-422-GUARD-COPIES
TC-ERR-09 disseminate() try/catch attestation Rejet E-500-ATTESTATION, ROLLBACK
TC-ERR-10 appendJournalEntryInTransaction Echec journal -> ROLLBACK
TC-ERR-11 disseminate() boucle gardes 1 doc invalide -> ROLLBACK global
TC-ERR-14 disseminate() garde retention Rejet E-409-RETENTION-DUE
TC-INV-05 disseminate() INSERT attestation 1 attestation par requete
TC-INV-11 disseminate() boucle + ROLLBACK Atomicite package
TC-INV-12 SELECT FOR UPDATE Concurrence geree
TC-INV-13 returnFromDissemination() GREATEST Ordre temporel garanti

10. Conformite code contract

Verification des interfaces declarees dans le code contract dip-service :

  • DisseminationService — classe @Injectable() creee
  • disseminate() — methode publique implementee
  • returnFromDissemination() — methode publique implementee
  • INV-278-02 a INV-278-14 — tous couverts (voir section 3)
  • Forbidden — tous respectes (voir section 4)
  • Architectural decisions — documentees (voir section 5)