PD-278 — Décomposition multi-agents (étape 6a)¶
Phase 0 — Validation préalable¶
H-06 : geo_copy_count ✅ VALIDɶ
geo_copy_count INTEGER NOT NULL DEFAULT 0existe dansDocumentSecureentity (ajouté par PD-279, DONE).- La spec PD-278 §3 définit
copies= "nombre de copies durables sur domaines de panne distincts, fourni par la couche stockage" — sémantiquement identique àgeo_copy_count.
H-01 : Stack technique ✅ VALIDɶ
- NestJS + TypeORM + PostgreSQL confirmé.
- Schéma :
vault_secure(documents),vault_integrity(journal).
V-01 : Enum DB 5 valeurs ✅ CONFIRMɶ
DocumentStatusenum TypeScript ={PENDING, SEALED, EXPIRED, RESTITUTED}.- Constantes hors enum :
DESTROYED,RECONCILIATION_FAILED. - PD-278 ajoutera
DIP→ l'enum PostgreSQL contiendra au moins 5 valeurs. - Le test TC-INV-01 doit vérifier
DIP ∈ enum_range, pas|enum| = 4.
V-03 : ALTER TYPE hors transaction ✅ CONFIRMɶ
- Pattern PD-279 :
ALTER TYPE vault_secure.document_status ADD VALUE IF NOT EXISTS 'RESTITUTED'— hors bloc BEGIN/COMMIT.
H-08 : BYPASSRLS retention_service → à vérifier implémentation mais documenté comme pattern PD-16.¶
TLA+ : ✅ DIP déjà dans le modèle formel¶
DocStates == {"SIP", "AIP", "DIP", "ELIMINATED"}— DIP est déjà présent dans NF_Z42_013.tla.RealStates == {"SIP", "AIP", "DIP", "ELIMINATED", "RESTITUTED", "DESTROYED", "RECONCILIATION_FAILED"}— DIP aussi.- CA-10 est déjà satisfait au niveau TLA+. L'action
CommunicateAIPetReturnDIPexistent.
Note migration timestamp¶
- Le timestamp
1741400000000est déjà pris par PD-280 (AddProofVerificationJobs). - Le fichier de migration PD-278 doit utiliser un timestamp supérieur, par exemple
1741500000000.
Découpage en tâches agents¶
Vue d'ensemble¶
| # | Agent ID | Module | Fichier(s) cible(s) | Dépendances |
|---|---|---|---|---|
| 1 | agent-migration | C1 | src/database/migrations/1741500000000-PD278-AddDipState.ts | Aucune |
| 2 | agent-entity | C2 | src/modules/documents/entities/document-secure.entity.ts + src/modules/documents/entities/dissemination-attestation.entity.ts | Agent 1 |
| 3 | agent-state-machine | C3 | src/modules/destruction/services/document-state-machine.service.ts | Agent 2 |
| 4 | agent-config | C4 | src/modules/documents/config/dissemination.config.ts | Aucune |
| 5 | agent-dto | C5 | src/modules/documents/dto/create-dissemination.dto.ts + src/modules/documents/dto/dissemination-response.dto.ts + src/modules/documents/dto/dissemination-error.dto.ts | Agent 4 |
| 6 | agent-audit | C6 | src/modules/audit/types/audit-action.types.ts | Aucune |
| 7 | agent-journal | C7 | src/modules/integrity/enums/journal-event-type.enum.ts | Aucune |
| 8 | agent-rate-limit | C8 | src/modules/documents/guards/dissemination-rate-limit.guard.ts | Agents 4, 6 |
| 9 | agent-core | C9 | src/modules/documents/services/dissemination.service.ts | Agents 2, 3, 4, 5, 6, 7 |
| 10 | agent-controller | C10 | src/modules/documents/controllers/dissemination.controller.ts | Agents 5, 8, 9 |
| 11 | agent-filter | C11 | src/modules/documents/filters/dissemination-audit-exception.filter.ts | Agent 6 |
| 12 | agent-module | — | src/modules/documents/documents.module.ts | Agents 2, 4, 8, 9, 10, 11 |
| 13 | agent-formal | C12 | ProbatioVault-doc/docs/normes/nf-z42-013/formal/NF_Z42_013.tla | Aucune |
| 14 | agent-tests | C13 | src/modules/documents/**/*.spec.ts | Tous |
Séquencement des phases¶
Phase 1 — Fondations (parallélisable)
├── Agent 1 (migration) ─┐
├── Agent 4 (config) ─┤ Indépendants entre eux
├── Agent 5 (dto) ─┤
├── Agent 6 (audit types) ─┤
├── Agent 7 (journal types) ─┤
└── Agent 13 (TLA+ formel) ─┘
Phase 2 — Entities + State machine (séquentiel après Phase 1)
├── Agent 2 (entity) ← dépend Agent 1
└── Agent 3 (state machine) ← dépend Agent 2
Phase 3 — Core métier (après Phase 2, partiellement parallélisable)
├── Agent 8 (rate-limit) ← dépend Agents 4, 6
├── Agent 9 (service core) ← dépend Agents 2, 3, 4, 5, 6, 7
└── Agent 11 (filter) ← dépend Agent 6
Phase 4 — Assemblage (après Phase 3)
├── Agent 10 (controller) ← dépend Agents 5, 8, 9
└── Agent 12 (module) ← dépend Agents 2, 4, 8, 9, 10, 11
Phase 5 — Tests (après Phase 4)
└── Agent 14 (tests) ← dépend Tous
Tâche 1 : agent-migration (C1)¶
Module : dip-migration Fichier : src/database/migrations/1741500000000-PD278-AddDipState.ts
Mission : Créer la migration DDL pour l'état DIP.
Séquence DDL (up) : 1. ALTER TYPE vault_secure.document_status ADD VALUE IF NOT EXISTS 'DIP' — HORS transaction 2. ALTER TABLE vault_secure.documents ADD COLUMN IF NOT EXISTS disseminated_at TIMESTAMPTZ NULL 3. ALTER TABLE vault_secure.documents ADD COLUMN IF NOT EXISTS disseminated_by UUID NULL 4. ALTER TABLE vault_secure.documents ADD COLUMN IF NOT EXISTS dissemination_returned_at TIMESTAMPTZ NULL 5. ALTER TABLE vault_secure.documents ADD COLUMN IF NOT EXISTS dissemination_package_id UUID NULL 6. ALTER TABLE vault_secure.documents ADD COLUMN IF NOT EXISTS motif_communication VARCHAR(1024) NULL 7. ALTER TABLE vault_secure.documents ADD COLUMN IF NOT EXISTS retention_due BOOLEAN NOT NULL DEFAULT false 8. CHECK constraint temporel : chk_dip_temporal_order — dissemination_returned_at IS NULL OR disseminated_at IS NULL OR dissemination_returned_at >= disseminated_at 9. Trigger WORM motif_communication : trg_motif_communication_worm via trg_motif_communication_immutable() 10. Index partiel : idx_documents_dip_status ON vault_secure.documents(status) WHERE status = 'DIP' 11. Index partiel : idx_documents_retention_due ON vault_secure.documents(retention_due) WHERE retention_due = true 12. Index : idx_documents_dissemination_package ON vault_secure.documents(dissemination_package_id) WHERE dissemination_package_id IS NOT NULL 13. Table vault_secure.dissemination_attestations (non partitionnée pour simplifier — dette technique documentée avec story destination) 14. Index attestations : idx_attestations_actor, idx_attestations_issued_at, idx_attestations_package
Down migration : - Guard : COUNT(*) WHERE status = 'DIP' > 0 → throw Error - DROP table dissemination_attestations - DROP trigger + function - DROP CHECK constraint - DROP colonnes - DROP indexes - Ne pas retirer la valeur enum (PostgreSQL ne supporte pas DROP VALUE)
Pattern de référence : 1741300000000-PD279-AddRestitutedStatus.ts
Contrats : - ❌ ALTER TYPE ADD VALUE dans un bloc BEGIN/COMMIT - ❌ Subquery dans clause WHERE d'un index partiel - ❌ Math.random() pour tout identifiant - ✅ IF NOT EXISTS pour idempotence
Tâche 2 : agent-entity (C2)¶
Module : dip-entity-extension + dip-attestation Fichiers : - src/modules/documents/entities/document-secure.entity.ts (modification) - src/modules/documents/entities/dissemination-attestation.entity.ts (création)
Mission : Étendre l'entity DocumentSecure et créer l'entity DisseminationAttestation.
Modifications DocumentSecure : 1. Ajouter DIP = 'DIP' dans l'enum DocumentStatus 2. Ajouter les colonnes TypeORM : - @Column({ type: 'timestamptz', nullable: true }) disseminated_at: Date | null - @Column({ type: 'uuid', nullable: true }) disseminated_by: string | null - @Column({ type: 'timestamptz', nullable: true }) dissemination_returned_at: Date | null - @Column({ type: 'uuid', nullable: true }) dissemination_package_id: string | null - @Column({ type: 'varchar', length: 1024, nullable: true }) motif_communication: string | null - @Column({ type: 'boolean', default: false }) retention_due: boolean
Création DisseminationAttestation : - Entity TypeORM conforme au schéma §5.13 de la spec - Champs : id, attestation_id, request_id, document_ids (UUID[]), actor_id, issued_at, motif_communication, dissemination_package_id, hash_evidence, signature_ref, created_at
Contrats : - ❌ Modifier les colonnes existantes de DocumentSecure - ❌ Supprimer/renommer des colonnes PD-279 - ❌ UPDATE sur hash_evidence ou signature_ref (WORM)
Tâche 3 : agent-state-machine (C3)¶
Module : dip-state-machine Fichier : src/modules/destruction/services/document-state-machine.service.ts (modification)
Mission : Ajouter les transitions DIP dans la machine à états.
Modifications : - ALLOWED_TRANSITIONS : ajouter DIP dans la valeur du set de SEALED : new Set([DocumentStatus.EXPIRED, DocumentStatus.RESTITUTED, DocumentStatus.DIP]) - Ajouter nouvelle entrée : [DocumentStatus.DIP, new Set([DocumentStatus.SEALED])]
Pattern existant :
const ALLOWED_TRANSITIONS: ReadonlyMap<string, ReadonlySet<string>> = new Map([
[DocumentStatus.PENDING, new Set([DocumentStatus.SEALED])],
[DocumentStatus.SEALED, new Set([DocumentStatus.EXPIRED, DocumentStatus.RESTITUTED, DocumentStatus.DIP])], // +DIP
[DocumentStatus.RESTITUTED, new Set([DocumentStatus.SEALED])],
[DocumentStatus.DIP, new Set([DocumentStatus.SEALED])], // NOUVEAU
[DocumentStatus.EXPIRED, new Set([DESTROYED, RECONCILIATION_FAILED])],
[DESTROYED, new Set<string>()],
[RECONCILIATION_FAILED, new Set<string>()],
]);
Contrats : - ❌ Modifier les transitions existantes PD-250/PD-279 - ❌ Ajouter une transition sortante de EXPIRED - ❌ Transition implicite timer/cron pour DIP → SEALED
Tâche 4 : agent-config (C4)¶
Module : dip-config Fichier : src/modules/documents/config/dissemination.config.ts (création)
Mission : Créer la configuration DIP avec validation Joi.
Bornes : | Paramètre | Env var | Default | Min | Max | |---|---|---:|---:|---:| | minCopies | DIP_MIN_COPIES | 2 | 2 | 10 | | maxPackageSize | DIP_MAX_PACKAGE_SIZE | 100 | 1 | 100 | | slaTargetSealedToDipMs | SLA_TARGET_SEALED_TO_DIP_MS | 2000 | 200 | 5000 | | slaTargetDipToSealedMs | SLA_TARGET_DIP_TO_SEALED_MS | 1500 | 200 | 5000 | | slaHardTimeoutMs | SLA_HARD_TIMEOUT_MS | 5000 | 1000 | 30000 | | rateLimitPerMinute | DIP_RATE_LIMIT_PER_MIN | 60 | 1 | 1000 | | quotaPerDay | DIP_QUOTA_PER_DAY | 1000 | 1 | 100000 |
Pattern : restitution.config.ts — interface + Joi schema + registerAs('dissemination', ...) + fail-fast au démarrage.
Contrats : - ❌ Valeurs magiques en dur dans le code - ❌ Clamping silencieux de valeurs hors bornes
Tâche 5 : agent-dto (C5)¶
Module : dip-dto Fichiers : - src/modules/documents/dto/create-dissemination.dto.ts (création) - src/modules/documents/dto/dissemination-response.dto.ts (création) - src/modules/documents/dto/dissemination-error.dto.ts (création)
Mission : Créer les DTOs de requête/réponse pour les endpoints DIP.
CreateDisseminationDto : - documents: string[] — @IsArray(), @ArrayMinSize(1), @ArrayMaxSize(100), @IsUUID('4', { each: true }) - motif_communication?: string — @IsOptional(), @IsString(), @MaxLength(1024) - Validation interdite en entrée : dissemination_package_id, disseminated_at, dissemination_returned_at - Utiliser @ApiProperty() avec descriptions/exemples
DisseminationResponseDto : - attestation_id, document_ids, disseminated_at, dissemination_package_id, etc.
DisseminationErrorDto : - Enum de codes d'erreur contractuels : E-400-ID-FORMAT, E-400-READONLY-FIELD, E-401-AUTH, E-403-ROLE, E-403-RLS, E-409-STATE, E-409-CONFLICT, E-409-TEMPORAL-ORDER, E-409-RETENTION-DUE, E-422-GUARD-COPIES, E-422-PACKAGE-SIZE, E-422-MOTIF-LENGTH, E-429-RATE-LIMIT, E-500-ATTESTATION, E-500-AUDIT, E-503-REDIS
Contrats : - ❌ Accepter des timestamps client - ❌ Accepter dissemination_package_id en entrée
Tâche 6 : agent-audit (C6)¶
Module : dip-audit-types Fichier : src/modules/audit/types/audit-action.types.ts (modification)
Mission : Ajouter 3 nouveaux types d'action audit.
Ajouts dans AuditActionType :
DOCUMENT_DISSEMINATED = 'document.disseminated',
DOCUMENT_RETURNED_FROM_DISSEMINATION = 'document.returned_from_dissemination',
DOCUMENT_DISSEMINATION_DENIED = 'document.dissemination_denied',
Nommage : Suivre le pattern existant domain.action (ex: document.restitute).
Contrats : - ❌ Modifier ou supprimer des AuditActionType existants
Tâche 7 : agent-journal (C7)¶
Module : dip-journal-types Fichier : src/modules/integrity/enums/journal-event-type.enum.ts (modification)
Mission : Ajouter 2 nouveaux types d'événement journal.
Ajouts dans JournalEventType :
Contrats : - ❌ Modifier ou supprimer des JournalEventType existants
Tâche 8 : agent-rate-limit (C8)¶
Module : dip-rate-limit Fichier : src/modules/documents/guards/dissemination-rate-limit.guard.ts (création)
Mission : Créer le guard rate-limit + quota journalier DIP.
Mécanisme : - Clé rate-limit : dip:rate:{actorId} — Redis INCR + EXPIRE 60s - Clé quota journalier : dip:quota:{actorId}:{YYYY-MM-DD} — Redis INCR + EXPIRE 86400s - Si Redis indisponible → fail-closed 503 E-503-REDIS - Rejet 429 avec Retry-After header
Pattern : LegalRateLimitGuard (PD-81).
Différences avec le pattern : - Ajouter le quota journalier (double compteur Redis) - Scope controller uniquement (pas APP_GUARD global) - Audit 429 délégué au filter C11 (pas dans le guard)
Contrats : - ❌ Enregistrer en APP_GUARD global - ❌ Fail-open si Redis down - ❌ Rate-limit par document_id
Tâche 9 : agent-core (C9)¶
Module : dip-service Fichier : src/modules/documents/services/dissemination.service.ts (création)
Mission : Implémenter le service core DIP — orchestration SEALED↔DIP.
Méthode disseminate(dto, actorId) : 1. Validation cardinalité (1..maxPackageSize) 2. queryRunner = dataSource.createQueryRunner() → connect() → startTransaction() 3. SET LOCAL app.current_user_id = actorId (RLS context) 4. SELECT * FROM vault_secure.documents WHERE id = ANY($1) ORDER BY id ASC FOR UPDATE — // LOCK-ORDER: document_id ASC (§5.9) 5. Vérification tous trouvés (sinon E-404/E-403-RLS) 6. Pour chaque document — gardes séquentielles : - état = SEALED ? (sinon E-409-STATE) - geo_copy_count >= minCopies ? (sinon E-422-GUARD-COPIES) - retention_due = false ? (sinon E-409-RETENTION-DUE) - Si un échoue → ROLLBACK global (INV-278-11) 7. package_id = documents.length > 1 ? randomUUID() : null 8. UPDATE status=DIP + colonnes DIP 9. INSERT attestation dans vault_secure.dissemination_attestations (même tx) 10. INSERT outbox audit DOCUMENT_DISSEMINATED (même tx) via appendJournalEntryInTransaction 11. commitTransaction() → AuditLogService.logAsync() (post-commit)
Méthode returnFromDissemination(documentId, actorId) : 1. QueryRunner + transaction 2. SET LOCAL RLS 3. SELECT FOR UPDATE — // LOCK-ORDER: document_id ASC (§5.9) 4. Guard état = DIP 5. UPDATE status=SEALED, dissemination_returned_at = GREATEST(NOW(), disseminated_at) 6. INSERT outbox audit DOCUMENT_RETURNED (même tx) 7. Commit + logAsync post-commit
Invariants critiques : - crypto.randomUUID() pour package_id, attestation_id, request_id (REX PD-63) - hash_evidence = SHA-256 des document_ids + actor_id + issued_at - signature_ref = stub // STUB: PD-37 — HSM signature probante
Pattern : RestitutionService (PD-279)
Contrats : - ❌ Promise.all sur les gardes - ❌ Mutation contenu documentaire en DIP - ❌ Attestation/audit hors de la transaction ACID - ❌ Timeout implicite DIP → SEALED - ❌ Math.random() - ❌ Cache applicatif pour retention_due
Tâche 10 : agent-controller (C10)¶
Module : dip-controller Fichier : src/modules/documents/controllers/dissemination.controller.ts (création)
Mission : Créer le controller REST pour les 2 endpoints DIP.
Endpoints : 1. POST /api/v1/documents/disseminations - Guards : @UseGuards(JwtAuthGuard, AuthorizationGuard, DisseminationRateLimitGuard) - Roles : @Roles('PA', 'SA', 'auditor') - Filter : @UseFilters(DisseminationAuditExceptionFilter) - Body : CreateDisseminationDto - Return : 201 Created + DisseminationResponseDto
POST /api/v1/documents/:id/dissemination-return- Guards :
@UseGuards(JwtAuthGuard, AuthorizationGuard) - Roles :
@Roles('PA', 'SA', 'auditor', 'retention_service') - Filter :
@UseFilters(DisseminationAuditExceptionFilter) - Param :
@Param('id', ParseUUIDPipe) id: string - Body : vide
- Return : 200 OK
Pattern : RestitutionController (PD-279)
Contrats : - ❌ GET endpoint qui modifie l'état - ❌ Bypass de ParseUUIDPipe - ❌ Enregistrement hors DocumentsModule
Tâche 11 : agent-filter (C11)¶
Module : dip-exception-filter Fichier : src/modules/documents/filters/dissemination-audit-exception.filter.ts (création)
Mission : Créer le filter pour audit SYNCHRONE des refus sécurité.
Capture : 401, 403, 429, 409 (si error_code === 'E-409-RETENTION-DUE')
Mécanisme (spec §5.8 — synchrone) : 1. QueryRunner dédié (pas le même que le service) 2. startTransaction() → INSERT audit DOCUMENT_DISSEMINATION_DENIED → commitTransaction() 3. Persistance garantie AVANT le retour de la réponse HTTP 4. En cas d'échec audit : log error mais ne pas masquer l'exception originale 5. Re-throw l'exception originale
Différence avec DepositAuditExceptionFilter : persistance SYNCHRONE via QueryRunner dédié au lieu de logAsync.
Contrats : - ❌ Swallow d'exceptions sans re-throw - ❌ logAsync pour les refus sécurité
Tâche 12 : agent-module (pas un agent séparé — intégré dans l'assemblage)¶
Fichier : src/modules/documents/documents.module.ts (modification)
Ajouts : - imports : ConfigModule.forFeature(disseminationConfig), TypeOrmModule.forFeature([..., DisseminationAttestation]) - controllers : DisseminationController - providers : DisseminationService, DisseminationRateLimitGuard, DisseminationAuditExceptionFilter - exports : DisseminationService (si nécessaire pour d'autres modules)
Tâche 13 : agent-formal (C12)¶
Module : dip-tla-formal Fichier : ProbatioVault-doc/docs/normes/nf-z42-013/formal/NF_Z42_013.tla
Observation : DIP est déjà présent dans le modèle TLA+. - DocStates == {"SIP", "AIP", "DIP", "ELIMINATED"} - Actions CommunicateAIP et ReturnDIP existent.
Mission : Vérifier que le modèle est complet et aligné avec la spec PD-278. - Confirmer que CommunicateAIP vérifie les gardes (copies >= MIN_COPIES) - Confirmer que ReturnDIP est explicite uniquement - Confirmer l'invariant DocStates ⊆ RealStates
CA-10 : potentiellement déjà satisfait — à valider par exécution TLC.
Tâche 14 : agent-tests (C13)¶
Module : dip-tests Fichiers : - src/modules/documents/services/dissemination.service.spec.ts — tests unitaires service - src/modules/documents/controllers/dissemination.controller.spec.ts — tests unitaires controller - src/modules/documents/guards/dissemination-rate-limit.guard.spec.ts — tests unitaires guard - src/modules/documents/filters/dissemination-audit-exception.filter.spec.ts — tests unitaires filter - src/modules/documents/__tests__/dissemination.integration.spec.ts — tests intégration DB
49 tests contractuels à couvrir : - TC-NOM-01 à TC-NOM-07 (7 nominaux) - TC-ERR-01 à TC-ERR-14 (14 erreurs) - TC-INV-01 à TC-INV-13 (13 invariants) - TC-NR-01 à TC-NR-06 (6 non-régression) - TC-NEG-01 à TC-NEG-07 (7 négatifs/adversariaux) - TC-FML-01, TC-FML-02 (2 formels)
Répartition : | Fichier | Tests | Niveau | |---|---|---| | dissemination.service.spec.ts | TC-NOM-01/02/03/06/07, TC-ERR-06/07/09/10/11/14, TC-INV-02/03/05/06/11/13 | Unit + mock DB | | dissemination.controller.spec.ts | TC-ERR-01/02/08A/08B, TC-NEG-01/02/03 | Unit | | dissemination-rate-limit.guard.spec.ts | TC-ERR-13 (rate-limit + fail-closed Redis) | Unit | | dissemination-audit-exception.filter.spec.ts | TC-INV-04 (audit refus synchrone) | Unit | | dissemination.integration.spec.ts | TC-NOM-01/02/03/04, TC-ERR-03/04/05/12, TC-INV-01/07/08/09/10/12, TC-NR-01/02/03/04/05/06, TC-NEG-04/05/06/07 | Integration DB |
Contrats : - ❌ Mocks qui masquent le comportement PostgreSQL réel pour les tests d'intégration - ❌ Tests dépendants d'un ordre non déterministe - ❌ Skip de tests concurrence ou atomicité - ✅ Framework Jest + ts-jest - ✅ Pattern .overrideGuard().useValue() pour tests unitaires controller
Matrice de couverture TC → Agent¶
| TC | Agent(s) responsable(s) |
|---|---|
| TC-INV-01 | agent-migration (enum DB), agent-entity (enum TS), agent-tests |
| TC-INV-02 | agent-core (gardes), agent-tests |
| TC-INV-03 | agent-core (pas de timer), agent-tests |
| TC-INV-04 | agent-core (outbox tx) + agent-filter (audit refus), agent-tests |
| TC-INV-05 | agent-core (attestation), agent-tests |
| TC-INV-06 | agent-migration (trigger WORM), agent-core (pas de mutation), agent-tests |
| TC-INV-07 | agent-core (SET LOCAL RLS), agent-tests |
| TC-INV-08 | agent-state-machine (EXPIRED terminal), agent-tests |
| TC-INV-09 | agent-state-machine (matrice complète), agent-tests |
| TC-INV-10 | agent-core (hash_evidence), agent-tests |
| TC-INV-11 | agent-core (ROLLBACK global), agent-tests |
| TC-INV-12 | agent-core (SELECT FOR UPDATE), agent-tests |
| TC-INV-13 | agent-core (GREATEST + retention), agent-tests |
| TC-NOM-* | agent-core + agent-controller, agent-tests |
| TC-ERR-* | agent-dto + agent-core + agent-rate-limit + agent-filter, agent-tests |
| TC-NR-* | agent-state-machine + agent-core, agent-tests |
| TC-NEG-* | agent-dto + agent-core + agent-migration, agent-tests |
| TC-FML-01/02 | agent-formal + agent-tests |
Points de vigilance (rappel)¶
| ID | Risque | Mitigation |
|---|---|---|
| V-01 | Enum DB 5 valeurs vs spec 4 | TC-INV-01 : vérifier DIP ∈ enum_range, pas |enum| = 4 |
| V-02 | Deadlock multi-modules | // LOCK-ORDER: document_id ASC (§5.9) dans chaque SELECT FOR UPDATE |
| V-03 | ALTER TYPE hors transaction | queryRunner.query() sans transaction wrapping |
| V-04 | Index partiels sans subquery | WHERE status = 'DIP' — prédicat simple OK |
| V-05 | Exception filter scope | Guards scopés controller → filter controller les capture |
| V-07 | crypto.randomUUID() obligatoire | import { randomUUID } from 'node:crypto' |
| V-08 | Stub geo_copy_count | PD-279 DONE — colonne disponible |
| V-10 | Down migration bloquée si DIP actif | Guard COUNT(*) dans down() |
| NOUVEAU | Timestamp migration 1741400000000 pris par PD-280 | Utiliser 1741500000000 |
| NOUVEAU | TLA+ DIP déjà présent | Valider alignment, pas besoin de modifier |
Résumé exécutif¶
- 14 tâches agents, 5 phases séquentielles avec parallélisme intra-phase
- 13 fichiers à créer/modifier dans
ProbatioVault-backend - 49 tests contractuels à implémenter
- Phase 1 : 7 agents parallèles (fondations)
- Phase 2 : 2 agents séquentiels (entities + state machine)
- Phase 3 : 3 agents partiellement parallèles (core métier)
- Phase 4 : 2 agents (assemblage controller + module)
- Phase 5 : 1 agent (tests — le plus volumineux)
- TLA+ : CA-10 potentiellement déjà satisfait — DIP est déjà dans
DocStates - Patterns réutilisés : RestitutionService (PD-279), LegalRateLimitGuard (PD-81), DepositAuditExceptionFilter (PD-60), migrations enum (PD-250/PD-279)