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
Plan rédigé le 2026-02-17 Workflow de gouvernance ProbatioVault