Aller au contenu

PD-82 — Plan d'implémentation

Date : 2026-02-17 Story : PD-82 - Implémenter validation double (parent + autorité) Complexité : HIGH (mécanisme cryptographique critique) Estimation : 8-10 tâches agents


1. Vue d'ensemble architecturale

1.1 Module structure

src/modules/dual-validation/
├── dual-validation.module.ts
├── controllers/
│   └── dual-validation.controller.ts
├── services/
│   ├── dual-validation.service.ts
│   ├── dual-validation-state-machine.service.ts
│   └── signature-verification.service.ts
├── entities/
│   ├── dual-validation-request.entity.ts
│   └── validation-record.entity.ts
├── dto/
│   ├── create-validation-request.dto.ts
│   ├── submit-validation.dto.ts
│   ├── revoke-validation.dto.ts
│   └── validation-response.dto.ts
├── interfaces/
│   ├── dual-validation.interfaces.ts
│   └── validation-event.interfaces.ts
├── enums/
│   ├── validation-state.enum.ts
│   └── validation-event-type.enum.ts
├── guards/
│   └── validator-identity.guard.ts
├── decorators/
│   └── validator-role.decorator.ts
└── __tests__/
    ├── dual-validation.service.spec.ts
    ├── state-machine.spec.ts
    └── e2e/
        └── dual-validation.e2e-spec.ts

1.2 Dépendances modules existants

Module Usage Interface
crypto/pre (PD-41) Activation PRE après validation 2-of-2 activatePRE(delegationId)
auth-audit (PD-31) Journalisation append-only AuthAuditWriterService.write()
auth/oidc Authentification forte OidcJwtValidationService

1.3 Décisions techniques (écarts Gate 3)

Écart Décision
ÉCART-01 TTL "7 jours" 168 heures UTC (7×24h) à partir du timestamp de la première validation
ÉCART-03 RFC 3161 RFC 3161 + TSR (Timestamp Response) comme format de référence
ÉCART-11 X.509/eIDAS X.509v3 + eIDAS level substantial ou high uniquement

2. Modèle de données

2.1 Entité DualValidationRequest

@Entity({ name: 'dual_validation_request', schema: 'vault_secure' })
export class DualValidationRequest {
  @PrimaryGeneratedColumn('uuid')
  id!: string;

  @Column({ type: 'uuid', name: 'delegation_id', unique: true })
  delegationId!: string;  // PRE delegation target

  @Column({ type: 'uuid', name: 'minor_vault_id' })
  minorVaultId!: string;

  @Column({ type: 'uuid', name: 'parent_user_id' })
  parentUserId!: string;

  @Column({ type: 'uuid', name: 'authority_id', nullable: true })
  authorityId?: string;  // Resolved from internal registry

  @Column({ type: 'varchar', length: 20, name: 'state' })
  state!: ValidationState;  // PENDING_BOTH, PENDING_AUTHORITY, etc.

  @Column({ type: 'timestamptz', precision: 3, name: 'created_at' })
  createdAt!: Date;

  @Column({ type: 'timestamptz', precision: 3, name: 'first_validation_at', nullable: true })
  firstValidationAt?: Date;  // TTL starts here

  @Column({ type: 'timestamptz', precision: 3, name: 'activated_at', nullable: true })
  activatedAt?: Date;

  @Column({ type: 'timestamptz', precision: 3, name: 'expires_at', nullable: true })
  expiresAt?: Date;  // firstValidationAt + 168h

  @OneToMany(() => ValidationRecord, (v) => v.request)
  validations!: ValidationRecord[];
}

2.2 Entité ValidationRecord

@Entity({ name: 'validation_record', schema: 'vault_secure' })
export class ValidationRecord {
  @PrimaryGeneratedColumn('uuid')
  id!: string;

  @ManyToOne(() => DualValidationRequest, (r) => r.validations)
  @JoinColumn({ name: 'request_id' })
  request!: DualValidationRequest;

  @Column({ type: 'uuid', name: 'request_id' })
  requestId!: string;

  @Column({ type: 'varchar', length: 20, name: 'validator_type' })
  validatorType!: 'PARENT' | 'AUTHORITY';

  @Column({ type: 'uuid', name: 'validator_id' })
  validatorId!: string;

  @Column({ type: 'timestamptz', precision: 3, name: 'validated_at' })
  validatedAt!: Date;  // Probatory timestamp

  @Column({ type: 'bytea', name: 'signature' })
  signature!: Buffer;  // Cryptographic signature

  @Column({ type: 'varchar', length: 32, name: 'signature_algorithm' })
  signatureAlgorithm!: string;  // e.g., 'Ed25519', 'ECDSA-P256'

  @Column({ type: 'text', name: 'certificate_chain' })
  certificateChain!: string;  // X.509v3 PEM chain

  @Column({ type: 'varchar', length: 20, name: 'eidas_level' })
  eidasLevel!: 'substantial' | 'high';

  @Column({ type: 'boolean', name: 'revoked', default: false })
  revoked!: boolean;

  @Column({ type: 'timestamptz', precision: 3, name: 'revoked_at', nullable: true })
  revokedAt?: Date;

  @Column({ type: 'char', length: 64, name: 'event_hash' })
  eventHash!: string;  // Link to audit log
}

2.3 Énumérations

export enum ValidationState {
  PENDING_BOTH = 'PENDING_BOTH',
  PENDING_AUTHORITY = 'PENDING_AUTHORITY',
  PENDING_PARENT = 'PENDING_PARENT',
  ACTIVATED = 'ACTIVATED',
  REJECTED = 'REJECTED',
  EXPIRED = 'EXPIRED',
}

export enum ValidationEventType {
  REQUEST_CREATED = 'DVAL_REQUEST_CREATED',
  PARENT_VALIDATED = 'DVAL_PARENT_VALIDATED',
  AUTHORITY_VALIDATED = 'DVAL_AUTHORITY_VALIDATED',
  VALIDATION_REVOKED = 'DVAL_VALIDATION_REVOKED',
  REQUEST_ACTIVATED = 'DVAL_REQUEST_ACTIVATED',
  REQUEST_EXPIRED = 'DVAL_REQUEST_EXPIRED',
  INVALID_ATTEMPT = 'DVAL_INVALID_ATTEMPT',
}

3. Interfaces de service

3.1 IDualValidationService

export interface IDualValidationService {
  /**
   * Creates a new dual-validation request for PRE activation.
   * INV-82-11: No PRE keys activated at creation.
   */
  createRequest(params: CreateRequestParams): Promise<DualValidationRequest>;

  /**
   * Submits a validation from parent or authority.
   * INV-82-06: Validation timestamped with probatory timestamp.
   * INV-82-09: Signature cryptographically verified.
   * INV-82-10: Identity non-contestable (X.509v3/eIDAS).
   */
  submitValidation(params: SubmitValidationParams): Promise<ValidationResult>;

  /**
   * Revokes own validation before activation.
   * INV-82-04: Revocation leads to REJECTED state.
   */
  revokeValidation(params: RevokeValidationParams): Promise<RevocationResult>;

  /**
   * Gets request status for tracking.
   */
  getRequestStatus(requestId: string): Promise<RequestStatusDto>;

  /**
   * Processes TTL expiration (called by scheduler).
   * INV-82-03: After 7 days, state becomes EXPIRED.
   */
  processExpirations(): Promise<number>;
}

3.2 IStateMachineService

export interface IStateMachineService {
  /**
   * Computes next state based on current state and event.
   * INV-82-01: Single validation never triggers activation.
   * INV-82-08: Order of validations is irrelevant.
   */
  transition(current: ValidationState, event: StateEvent): ValidationState;

  /**
   * Checks if a transition is valid.
   * INV-82-04: No transition from terminal states.
   */
  canTransition(current: ValidationState, target: ValidationState): boolean;

  /**
   * Returns terminal states.
   */
  getTerminalStates(): ValidationState[];
}

4. Code Contracts

CC-82-01 — Entity Layer

contract_id: CC-82-01
name: "Dual Validation Entities"
scope:
  - dual-validation-request.entity.ts
  - validation-record.entity.ts
invariants_covered: [INV-82-06, INV-82-07, INV-82-09, INV-82-10]
constraints:
  - "Timestamps with precision 3 (milliseconds)"
  - "Signature stored as bytea (never decoded to string)"
  - "Certificate chain as PEM text"
  - "eIDAS level enum: substantial | high only"
  - "event_hash links to auth_audit_log"
acceptance_criteria: [CA-82-04, CA-82-05]

CC-82-02 — State Machine

contract_id: CC-82-02
name: "Validation State Machine"
scope:
  - dual-validation-state-machine.service.ts
invariants_covered: [INV-82-01, INV-82-02, INV-82-03, INV-82-04, INV-82-08]
constraints:
  - "ACTIVATED only reachable with 2 valid, non-revoked validations"
  - "No implicit transitions (no auto-approve)"
  - "TTL = 168 hours UTC from firstValidationAt"
  - "Terminal states: ACTIVATED, REJECTED, EXPIRED (no outbound transitions)"
  - "Order agnostic: PENDING_AUTHORITY and PENDING_PARENT symmetric"
acceptance_criteria: [CA-82-01, CA-82-02, CA-82-03, CA-82-06, CA-82-10]

CC-82-03 — Validation Service

contract_id: CC-82-03
name: "Dual Validation Service"
scope:
  - dual-validation.service.ts
invariants_covered: [INV-82-01, INV-82-05, INV-82-11, INV-82-12]
constraints:
  - "activatePRE(delegationId) called only when state === ACTIVATED"
  - "activatePRE called exactly once per request (idempotency check)"
  - "All events logged via AuthAuditWriterService"
  - "Platform cannot bypass validation requirement"
acceptance_criteria: [CA-82-01, CA-82-07, CA-82-08, CA-82-09]
dependencies:
  - "PreService.activatePRE (PD-41)"
  - "AuthAuditWriterService.write (PD-31)"

CC-82-04 — Signature Verification

contract_id: CC-82-04
name: "Signature & Certificate Verification"
scope:
  - signature-verification.service.ts
invariants_covered: [INV-82-09, INV-82-10]
constraints:
  - "Verify signature against certificate public key"
  - "Validate certificate chain (root CA trusted)"
  - "Check eIDAS level >= substantial"
  - "Reject expired/revoked certificates"
  - "Log verification failures to audit"
acceptance_criteria: [CA-82-04, CA-82-05]

CC-82-05 — Audit Integration

contract_id: CC-82-05
name: "Audit Log Integration"
scope:
  - dual-validation.service.ts (audit calls)
invariants_covered: [INV-82-12]
constraints:
  - "Every state transition logged append-only"
  - "Invalid attempts logged with reason"
  - "Activation event references both validation IDs"
  - "Hash chain integrity (via PD-31)"
acceptance_criteria: [CA-82-08, CA-82-09]
event_types:
  - DVAL_REQUEST_CREATED
  - DVAL_PARENT_VALIDATED
  - DVAL_AUTHORITY_VALIDATED
  - DVAL_VALIDATION_REVOKED
  - DVAL_REQUEST_ACTIVATED
  - DVAL_REQUEST_EXPIRED
  - DVAL_INVALID_ATTEMPT

CC-82-06 — Controller & DTOs

contract_id: CC-82-06
name: "HTTP API Layer"
scope:
  - dual-validation.controller.ts
  - dto/*.ts
  - guards/validator-identity.guard.ts
invariants_covered: [INV-82-05, INV-82-07]
constraints:
  - "POST /dual-validation/requests - create request"
  - "POST /dual-validation/requests/:id/validations - submit validation"
  - "DELETE /dual-validation/validations/:id - revoke own validation"
  - "GET /dual-validation/requests/:id - status"
  - "Guard ensures validator identity matches token"
  - "Authority ID never from user input (INV-82-05/CA-82-07)"
acceptance_criteria: [CA-82-07]

CC-82-07 — TTL Scheduler

contract_id: CC-82-07
name: "Expiration Scheduler"
scope:
  - expiration.scheduler.ts (new file)
invariants_covered: [INV-82-02, INV-82-03]
constraints:
  - "Cron job runs every 5 minutes"
  - "Transitions PENDING_* to EXPIRED if now > expiresAt"
  - "No implicit validation (explicit expiration only)"
  - "Logs expiration events"
acceptance_criteria: [CA-82-03]

CC-82-08 — Unit Tests

contract_id: CC-82-08
name: "Unit Test Suite"
scope:
  - __tests__/dual-validation.service.spec.ts
  - __tests__/state-machine.spec.ts
test_coverage:
  nominal:
    - TC-NOM-01: Single validation does not activate
    - TC-NOM-02: Parent→Authority activation
    - TC-NOM-03: Authority→Parent activation
    - TC-NOM-04: Probatory trail complete
  error:
    - TC-ERR-01: Activation attempt with single validation
    - TC-ERR-02: Validation after TTL expiry
    - TC-ERR-03: Revocation by non-author
    - TC-ERR-04: Revocation after ACTIVATED
    - TC-ERR-05: Invalid signature
    - TC-ERR-06: Missing certificate
    - TC-ERR-07: Parent selects authority
    - TC-ERR-08: Append-only mutation attempt
acceptance_criteria: [CA-82-01 through CA-82-10]

5. Séquence d'implémentation

Phase 1 — Foundation (Tâches 1-3)

Tâche Agent Contract Description
1 agent-developer CC-82-01 Entities + Enums + Module setup
2 agent-developer CC-82-02 State Machine service
3 agent-developer CC-82-04 Signature verification service

Phase 2 — Core Logic (Tâches 4-6)

Tâche Agent Contract Description
4 agent-developer CC-82-03 Dual Validation service
5 agent-developer CC-82-05 Audit log integration
6 agent-developer CC-82-07 TTL expiration scheduler

Phase 3 — API & Tests (Tâches 7-10)

Tâche Agent Contract Description
7 agent-developer CC-82-06 Controller + DTOs + Guards
8 agent-qa-unit CC-82-08 Unit tests (state machine, service)
9 agent-qa-unit CC-82-08 Integration tests (PD-41, PD-31)
10 agent-qa-unit CC-82-08 E2E tests (API endpoints)

6. Intégration avec PD-41 (PRE)

6.1 Interface requise

// Extension interface pour PD-82
export interface IPreActivationService {
  /**
   * Activates PRE delegation after 2-of-2 validation.
   * Called by DualValidationService when state === ACTIVATED.
   * Must be idempotent (HYP-82-01).
   */
  activatePRE(delegationId: string): Promise<PreActivationResult>;
}

6.2 Appel dans DualValidationService

private async triggerActivation(request: DualValidationRequest): Promise<void> {
  // INV-82-01: Verify both validations present
  const validations = await this.getValidValidations(request.id);
  if (validations.length !== 2) {
    throw new DualValidationError('ACTIVATION_REQUIRES_TWO_VALIDATIONS');
  }

  // INV-82-11: Only activate if state is ACTIVATED
  if (request.state !== ValidationState.ACTIVATED) {
    throw new DualValidationError('INVALID_STATE_FOR_ACTIVATION');
  }

  // Call PD-41 PRE module
  await this.preService.activatePRE(request.delegationId);

  // Log activation event (CA-82-09)
  await this.auditWriter.write({
    eventType: ValidationEventType.REQUEST_ACTIVATED,
    eventPayloadCanonical: {
      requestId: request.id,
      delegationId: request.delegationId,
      validationIds: validations.map(v => v.id),
    },
  });
}

7. Intégration avec PD-31 (Audit)

7.1 Extension event types

Ajouter dans auth-audit.interfaces.ts :

export enum AuthEventType {
  // ... existing types ...
  DVAL_REQUEST_CREATED = 'DVAL_REQUEST_CREATED',
  DVAL_PARENT_VALIDATED = 'DVAL_PARENT_VALIDATED',
  DVAL_AUTHORITY_VALIDATED = 'DVAL_AUTHORITY_VALIDATED',
  DVAL_VALIDATION_REVOKED = 'DVAL_VALIDATION_REVOKED',
  DVAL_REQUEST_ACTIVATED = 'DVAL_REQUEST_ACTIVATED',
  DVAL_REQUEST_EXPIRED = 'DVAL_REQUEST_EXPIRED',
  DVAL_INVALID_ATTEMPT = 'DVAL_INVALID_ATTEMPT',
}

7.2 Payload structure

interface DvalEventPayload {
  requestId: string;
  delegationId: string;
  minorVaultId: string;
  validatorType?: 'PARENT' | 'AUTHORITY';
  validatorId?: string;
  validationIds?: string[];  // For activation event
  reason?: string;           // For invalid attempts
  previousState?: ValidationState;
  newState?: ValidationState;
}

8. Migration SQL

-- Create schema if not exists
CREATE SCHEMA IF NOT EXISTS vault_secure;

-- Dual validation request table
CREATE TABLE vault_secure.dual_validation_request (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  delegation_id UUID NOT NULL UNIQUE,
  minor_vault_id UUID NOT NULL,
  parent_user_id UUID NOT NULL,
  authority_id UUID,
  state VARCHAR(20) NOT NULL DEFAULT 'PENDING_BOTH',
  created_at TIMESTAMPTZ(3) NOT NULL DEFAULT NOW(),
  first_validation_at TIMESTAMPTZ(3),
  activated_at TIMESTAMPTZ(3),
  expires_at TIMESTAMPTZ(3),
  CONSTRAINT chk_state CHECK (state IN (
    'PENDING_BOTH', 'PENDING_AUTHORITY', 'PENDING_PARENT',
    'ACTIVATED', 'REJECTED', 'EXPIRED'
  ))
);

-- Validation record table
CREATE TABLE vault_secure.validation_record (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  request_id UUID NOT NULL REFERENCES vault_secure.dual_validation_request(id),
  validator_type VARCHAR(20) NOT NULL,
  validator_id UUID NOT NULL,
  validated_at TIMESTAMPTZ(3) NOT NULL,
  signature BYTEA NOT NULL,
  signature_algorithm VARCHAR(32) NOT NULL,
  certificate_chain TEXT NOT NULL,
  eidas_level VARCHAR(20) NOT NULL,
  revoked BOOLEAN NOT NULL DEFAULT FALSE,
  revoked_at TIMESTAMPTZ(3),
  event_hash CHAR(64) NOT NULL,
  CONSTRAINT chk_validator_type CHECK (validator_type IN ('PARENT', 'AUTHORITY')),
  CONSTRAINT chk_eidas_level CHECK (eidas_level IN ('substantial', 'high'))
);

-- Indexes
CREATE INDEX idx_dval_request_state ON vault_secure.dual_validation_request(state);
CREATE INDEX idx_dval_request_expires ON vault_secure.dual_validation_request(expires_at)
  WHERE state IN ('PENDING_AUTHORITY', 'PENDING_PARENT');
CREATE INDEX idx_validation_request ON vault_secure.validation_record(request_id);

-- Append-only trigger (no UPDATE/DELETE on validation_record)
CREATE OR REPLACE FUNCTION vault_secure.prevent_validation_mutation()
RETURNS TRIGGER AS $$
BEGIN
  IF TG_OP = 'DELETE' THEN
    RAISE EXCEPTION 'DELETE not allowed on validation_record (append-only)';
  END IF;
  IF TG_OP = 'UPDATE' AND OLD.id IS NOT NULL THEN
    -- Allow only revoked flag update
    IF NEW.signature != OLD.signature OR NEW.certificate_chain != OLD.certificate_chain THEN
      RAISE EXCEPTION 'UPDATE not allowed on immutable fields (append-only)';
    END IF;
  END IF;
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_validation_append_only
BEFORE UPDATE OR DELETE ON vault_secure.validation_record
FOR EACH ROW EXECUTE FUNCTION vault_secure.prevent_validation_mutation();

9. Diagrammes Mermaid

9.1 Graphe de dependances inter-modules

graph TD
    subgraph "PD-82 — dual-validation"
        CTRL["dual-validation.controller.ts<br/>(CC-82-06)"]
        SVC["dual-validation.service.ts<br/>(CC-82-03)"]
        SM["dual-validation-state-machine.service.ts<br/>(CC-82-02)"]
        SIG["signature-verification.service.ts<br/>(CC-82-04)"]
        SCHED["expiration.scheduler.ts<br/>(CC-82-07)"]
        ENT_REQ["DualValidationRequest entity<br/>(CC-82-01)"]
        ENT_REC["ValidationRecord entity<br/>(CC-82-01)"]
        GUARD["validator-identity.guard.ts<br/>(CC-82-06)"]
        DTO["DTOs<br/>(CC-82-06)"]
    end

    subgraph "Modules externes"
        PRE["PreService.activatePRE<br/>(PD-41)"]
        AUDIT["AuthAuditWriterService.write<br/>(PD-31)"]
        OIDC["OidcJwtValidationService<br/>(auth/oidc)"]
    end

    CTRL --> GUARD
    CTRL --> DTO
    CTRL --> SVC
    GUARD --> OIDC

    SVC --> SM
    SVC --> SIG
    SVC --> ENT_REQ
    SVC --> ENT_REC
    SVC --> PRE
    SVC --> AUDIT

    SCHED --> SVC
    SCHED --> ENT_REQ

    SIG --> ENT_REC

    ENT_REQ --> ENT_REC

    classDef external fill:#f9f,stroke:#333,stroke-width:2px
    class PRE,AUDIT,OIDC external

9.2 Diagramme de sequence — Flux de validation double (Parent puis Autorite)

sequenceDiagram
    autonumber
    participant P as Parent
    participant API as DualValidation Controller
    participant SVC as DualValidation Service
    participant SM as StateMachine Service
    participant SIG as Signature Verification
    participant DB as PostgreSQL (vault_secure)
    participant AUDIT as AuthAuditWriter (PD-31)
    participant PRE as PreService (PD-41)

    Note over P,PRE: Phase 1 — Creation de la requete
    P->>API: POST /dual-validation/requests
    API->>SVC: createRequest(params)
    SVC->>SM: transition(_, REQUEST_CREATED)
    SM-->>SVC: PENDING_BOTH
    SVC->>DB: INSERT dual_validation_request (state=PENDING_BOTH)
    SVC->>AUDIT: write(DVAL_REQUEST_CREATED)
    SVC-->>API: DualValidationRequest
    API-->>P: 201 Created

    Note over P,PRE: Phase 2 — Validation du parent
    P->>API: POST /dual-validation/requests/:id/validations
    API->>SVC: submitValidation(params)
    SVC->>SIG: verifySignature(signature, certificate)
    SIG->>SIG: Validate X.509v3 chain + eIDAS level
    SIG-->>SVC: SignatureValid
    SVC->>SM: transition(PENDING_BOTH, PARENT_VALIDATED)
    SM-->>SVC: PENDING_AUTHORITY
    SVC->>DB: INSERT validation_record + UPDATE state=PENDING_AUTHORITY
    SVC->>DB: SET first_validation_at, expires_at (now + 168h)
    SVC->>AUDIT: write(DVAL_PARENT_VALIDATED)
    SVC-->>API: ValidationResult
    API-->>P: 200 OK

    Note over P,PRE: Phase 3 — Validation de l'autorite
    participant A as Autorite
    A->>API: POST /dual-validation/requests/:id/validations
    API->>SVC: submitValidation(params)
    SVC->>SIG: verifySignature(signature, certificate)
    SIG-->>SVC: SignatureValid
    SVC->>SM: transition(PENDING_AUTHORITY, AUTHORITY_VALIDATED)
    SM-->>SVC: ACTIVATED
    SVC->>DB: INSERT validation_record + UPDATE state=ACTIVATED, activated_at
    SVC->>AUDIT: write(DVAL_AUTHORITY_VALIDATED)

    Note over SVC,PRE: Phase 4 — Activation PRE (INV-82-01 : 2-of-2 requis)
    SVC->>SVC: getValidValidations(requestId) == 2
    SVC->>PRE: activatePRE(delegationId)
    PRE-->>SVC: PreActivationResult
    SVC->>AUDIT: write(DVAL_REQUEST_ACTIVATED)
    SVC-->>API: ValidationResult (ACTIVATED)
    API-->>A: 200 OK

9.3 Diagramme de sequence — Expiration TTL (scheduler)

sequenceDiagram
    autonumber
    participant CRON as Cron (toutes les 5 min)
    participant SCHED as Expiration Scheduler
    participant SVC as DualValidation Service
    participant SM as StateMachine Service
    participant DB as PostgreSQL (vault_secure)
    participant AUDIT as AuthAuditWriter (PD-31)

    CRON->>SCHED: trigger()
    SCHED->>DB: SELECT * FROM dual_validation_request<br/>WHERE state IN ('PENDING_AUTHORITY','PENDING_PARENT')<br/>AND expires_at < NOW()
    DB-->>SCHED: expired_requests[]

    loop Pour chaque requete expiree
        SCHED->>SVC: processExpiration(request)
        SVC->>SM: transition(PENDING_*, EXPIRED)
        SM-->>SVC: EXPIRED
        SVC->>DB: UPDATE state=EXPIRED
        SVC->>AUDIT: write(DVAL_REQUEST_EXPIRED)
    end

    SCHED-->>CRON: count(expired)

9.4 Machine d'etats — Transitions de validation

stateDiagram-v2
    [*] --> PENDING_BOTH : REQUEST_CREATED

    PENDING_BOTH --> PENDING_AUTHORITY : PARENT_VALIDATED
    PENDING_BOTH --> PENDING_PARENT : AUTHORITY_VALIDATED

    PENDING_AUTHORITY --> ACTIVATED : AUTHORITY_VALIDATED
    PENDING_PARENT --> ACTIVATED : PARENT_VALIDATED

    PENDING_BOTH --> REJECTED : VALIDATION_REVOKED
    PENDING_AUTHORITY --> REJECTED : VALIDATION_REVOKED
    PENDING_PARENT --> REJECTED : VALIDATION_REVOKED

    PENDING_AUTHORITY --> EXPIRED : TTL_EXCEEDED (168h)
    PENDING_PARENT --> EXPIRED : TTL_EXCEEDED (168h)

    note right of ACTIVATED : Terminal — PRE activee
    note right of REJECTED : Terminal — revocation
    note right of EXPIRED : Terminal — TTL 168h depasse

10. Risques et mitigations

Risque Probabilité Impact Mitigation
PD-41 activatePRE non idempotent Low Critical Test double-call, add idempotency key
Registre autorités indisponible Medium High Circuit breaker, graceful degradation
Horodatage non fiable Low Critical TSA externe (RFC 3161)
Signature verification timeout Medium Medium Async verification with retry
Race condition validations Low Medium Advisory locks (PD-31 pattern)

11. Checklist pre-implémentation

  • PD-41 exposes activatePRE(delegationId) interface
  • PD-31 audit log accepts new event types
  • Authority registry exists (even mock for Phase 1)
  • Certificate verification service available
  • TSA endpoint configured (RFC 3161)

Plan rédigé le 2026-02-17 Workflow de gouvernance ProbatioVault