PD-278 — Agent Developer Report: dip-controller¶
Module:
dip-controllerAgent: agent-developer Date: 2026-03-01 Story: PD-278 — NF Z42-013: ajout contractuel de l'etat DIP
1. Fichiers modifies¶
| Fichier | Action | Description |
|---|---|---|
src/modules/documents/controllers/dissemination.controller.ts | CREE | Controller REST DIP avec 2 endpoints POST |
src/modules/documents/documents.module.ts | MODIFIE | Enregistrement controller + service + guard + filter + config + entity attestation |
2. Implementation du controller¶
2.1 Endpoint F1 — POST /documents/disseminations (SEALED -> DIP)¶
POST /api/v1/documents/disseminations
Guards: JwtAuthGuard -> AuthorizationGuard(PA, SA, auditor) -> DisseminationRateLimitGuard
Body: CreateDisseminationDto { documents: UUID[], motif_communication?: string }
Response: 201 Created + DisseminationResponseDto
Invariants satisfaits: - INV-278-02: gardes chain JwtAuthGuard + AuthorizationGuard + DisseminationRateLimitGuard + gardes metier dans le service - INV-278-05: 1 attestation par requete (delegue au service) - INV-278-11: package atomique (delegue au service) - CA-04: actorId extrait de user.sub via @CurrentUser() (jamais du body) - CA-08: bornes documents[] 1..100 via class-validator dans CreateDisseminationDto
2.2 Endpoint F2 — POST /documents/:id/dissemination-return (DIP -> SEALED)¶
POST /api/v1/documents/{id}/dissemination-return
Guards: JwtAuthGuard -> AuthorizationGuard(PA, SA, auditor, retention_service)
Param: id validated by ParseUUIDPipe (E-400-ID-FORMAT)
Body: DisseminationReturnDto (empty)
Response: 200 OK + DisseminationReturnResponseDto
Invariants satisfaits: - INV-278-03: retour explicite uniquement, pas de timeout implicite - INV-278-13: returned_at >= disseminated_at (delegue au service via GREATEST) - INV-278-14: role retention_service autorise pour anti-contournement retention
2.3 Exception filter (INV-278-04)¶
Le controller est decore @UseFilters(DisseminationAuditExceptionFilter) au niveau classe. Cela capture les exceptions des guards decores via @UseGuards() car dans NestJS, les guards declares sur le controller sont dans l'exception zone du filter du meme controller.
Refus audites synchroniquement: 401 (auth), 403 (role/RLS), 429 (rate-limit), 409-RETENTION-DUE.
3. Enregistrement module (DocumentsModule)¶
Modifications dans documents.module.ts:
| Element | Section | Ajout |
|---|---|---|
disseminationConfig | imports | ConfigModule.forFeature(disseminationConfig) |
DisseminationAttestation | imports | TypeOrmModule.forFeature([..., DisseminationAttestation]) |
DisseminationController | controllers | Ajout dans le tableau controllers |
DisseminationService | providers | Provider injectable |
DisseminationAuditExceptionFilter | providers | Requis pour @UseFilters() (pattern PD-60) |
DisseminationRateLimitGuard | providers | Provider injectable (Redis dependency via DI) |
4. Verification invariants code contract¶
| Invariant | Statut | Mecanisme |
|---|---|---|
| POST /api/v1/documents/disseminations pour SEALED -> DIP | OK | @Post('disseminations') + @HttpCode(HttpStatus.CREATED) |
| POST /api/v1/documents/{id}/dissemination-return pour DIP -> SEALED | OK | @Post(':id/dissemination-return') + @HttpCode(HttpStatus.OK) |
| Guards F1: JwtAuthGuard + AuthorizationGuard + DisseminationRateLimitGuard | OK | @UseGuards(JwtAuthGuard, AuthorizationGuard, DisseminationRateLimitGuard) sur disseminate() |
| Guards F2: JwtAuthGuard + AuthorizationGuard (roles incluant retention_service) | OK | @UseGuards(JwtAuthGuard, AuthorizationGuard) + @Roles('PA', 'SA', 'auditor', 'retention_service') sur returnFromDissemination() |
5. Verification forbidden¶
| Forbidden | Statut | Justification |
|---|---|---|
| GET endpoint qui modifie l'etat | OK | Aucun @Get() dans le controller — uniquement @Post() |
| Bypass de ParseUUIDPipe sur document_id | OK | @Param('id', ParseUUIDPipe) sur F2 ; documents[] valides par @IsUUID('4', { each: true }) dans DTO |
| Enregistrement dans un module autre que DocumentsModule | OK | Controller enregistre dans DocumentsModule.controllers |
6. Decisions architecturales¶
architectural_decisions:
- decision: "Delegation totale de la logique metier au DisseminationService"
rationale: "Pattern RestitutionController etabli — controller thin, service fat. Separer validation HTTP de la logique ACID."
alternatives_considered: ["Logique dans le controller", "Middleware chain"]
trade_offs: ["Controller simple et testable unitairement, mais ajout d'un appel supplementaire"]
- decision: "@UseFilters au niveau classe (pas par methode)"
rationale: "Les deux endpoints F1/F2 doivent auditer les refus securite — scope classe evite la duplication"
alternatives_considered: ["@UseFilters par methode", "APP_GUARD global"]
trade_offs: ["Filter applique a tout le controller, mais c'est le comportement voulu"]
7. Matrice de couverture tests (controller)¶
Le controller lui-meme est un thin wrapper. Les tests contractuels ci-dessous le concernent via les tests d'integration:
| Test ID | Couverture controller | Fichier test |
|---|---|---|
| TC-NOM-01 | Flux F1 complet (route + guards + service) | dissemination.controller.spec.ts |
| TC-NOM-03 | Flux F2 complet (route + guards + service) | dissemination.controller.spec.ts |
| TC-ERR-01 | ParseUUIDPipe rejet UUID invalide (F2) | dissemination.controller.spec.ts |
| TC-ERR-02 | DTO rejette champs serveur interdits (F1) | dissemination.controller.spec.ts |
| TC-ERR-03 | JwtAuthGuard rejet 401 + audit filter | dissemination.controller.spec.ts |
| TC-ERR-04 | AuthorizationGuard rejet 403 + audit filter | dissemination.controller.spec.ts |
| TC-ERR-08A | DTO ArrayMinSize(1) rejet documents=[] | dissemination.controller.spec.ts |
| TC-ERR-08B | DTO ArrayMaxSize(100) rejet documents[101] | dissemination.controller.spec.ts |
| TC-ERR-12 | Role non autorise pour F2 (DIP -> SEALED) | dissemination.controller.spec.ts |
| TC-ERR-13 | Rate-limit guard rejet 429 + audit filter | dissemination.controller.spec.ts |
8. Hypotheses et dependances¶
| ID | Hypothese | Statut |
|---|---|---|
| H-02 | Roles IAM PA, SA, auditor, retention_service existent | Confirme: AuthorizationGuard lit les roles via @Roles() + Reflector |
| V-05 | Exception filter scope: guards decores @UseGuards() sont dans l'exception zone de @UseFilters() du meme controller | Confirme: pattern NestJS standard |
9. Verification TypeScript¶
10. Fichiers hors perimetre identifies¶
Aucun besoin de modification hors perimetre identifie. Toutes les dependances (service, guard, filter, DTOs, config, entities) existent deja et sont consommees telles quelles.