PD-278 — Agent Developer : dip-migration
Module
dip-migration — Migration DDL pour l'ajout de l'etat DIP au cycle de vie documentaire.
Fichier produit
src/database/migrations/1741500000000-PD278-AddDipState.ts
Resume des operations
up()
| Phase | Operation | Invariant | Idempotent |
| 1 | ALTER TYPE vault_secure.document_status ADD VALUE IF NOT EXISTS 'DIP' | INV-278-01 | Oui (IF NOT EXISTS) |
| 2 | Ajout 6 colonnes : disseminated_at, disseminated_by, dissemination_returned_at, dissemination_package_id, motif_communication, retention_due | CA-04, CA-12, INV-278-14 | Oui (IF NOT EXISTS) |
| 3 | CHECK constraint chk_dip_temporal_order : returned_at >= disseminated_at | INV-278-13 | Non (constraint named) |
| 4 | Trigger WORM trg_motif_communication_worm bloque UPDATE motif_communication apres insertion | INV-278-06 | Oui (CREATE OR REPLACE + DROP IF EXISTS) |
| 5 | 3 index partiels : idx_documents_dip_status, idx_documents_retention_due, idx_documents_dissemination_package | Performance | Oui (IF NOT EXISTS) |
| 6 | Table vault_secure.dissemination_attestations partitionnee RANGE(issued_at), 2 partitions (2025, 2026) | INV-278-05 | Oui (IF NOT EXISTS) |
| 7 | 3 index attestations : idx_attestations_actor, idx_attestations_issued_at, idx_attestations_package | Performance | Oui (IF NOT EXISTS) |
| 8 | Trigger append-only trg_dissemination_attestations_worm bloque UPDATE et DELETE sur attestations | INV-278-05, spec SS5.13 | Oui (CREATE OR REPLACE + DROP IF EXISTS) |
| 9 | REVOKE TRUNCATE ON vault_secure.dissemination_attestations FROM PUBLIC | Defense en profondeur | Oui (idempotent) |
down()
- Garde bloquante :
COUNT(*) WHERE status = 'DIP' > 0 → exception avec message explicite - Drop en ordre inverse : trigger WORM attestations → index attestations → partitions (CASCADE) → table attestations (CASCADE) → index documents → trigger/function WORM motif → CHECK constraint → colonnes
- L'enum
DIP reste dans le type PostgreSQL (DROP VALUE n'existe pas)
Invariants couverts
| Invariant | Mecanisme | Verification |
| INV-278-01 | ALTER TYPE ADD VALUE IF NOT EXISTS 'DIP' | SELECT unnest(enum_range(NULL::vault_secure.document_status)) contient DIP |
| INV-278-05 | CREATE TABLE vault_secure.dissemination_attestations PARTITION BY RANGE (issued_at) + trigger append-only + REVOKE TRUNCATE | Table existe, 2 partitions 2025/2026, UPDATE/DELETE rejetes, TRUNCATE revoque |
| INV-278-06 | Trigger trg_motif_communication_worm + function trg_motif_communication_immutable() | UPDATE motif apres INSERT → restrict_violation |
| INV-278-13 | CHECK chk_dip_temporal_order | INSERT avec returned_at < disseminated_at → rejet |
| INV-278-14 | Colonne retention_due BOOLEAN NOT NULL DEFAULT false | Colonne existe, default false |
| Down guard | COUNT(*) WHERE status = 'DIP' > 0 → exception | Down avec DIP actif → erreur |
Interdits respectes
| Interdit | Verification |
| ALTER TYPE ADD VALUE dans BEGIN/COMMIT | queryRunner.query() direct, pas de startTransaction() autour |
| Subquery dans WHERE d'index partiel (REX PD-55) | Predicats simples uniquement (WHERE status = 'DIP', WHERE retention_due = true, WHERE ... IS NOT NULL) |
| DROP TYPE document_status | Absent du down() — commentaire explicite |
| Math.random() | Aucun appel — identifiants generes par gen_random_uuid() PostgreSQL |
Decisions architecturales
| Decision | Justification | Alternatives considerees | Trade-offs |
| ALTER TYPE hors transaction + colonnes idempotentes IF NOT EXISTS | Pattern etabli PD-250/PD-279, PostgreSQL contraint ALTER TYPE ADD VALUE hors tx | Nouvelle table de mapping status, Type VARCHAR au lieu d'enum | Enum natif = performance + integrite, mais non supprimable |
| Table dediee dissemination_attestations avec partitioning RANGE(issued_at) | Schema riche SS5.13, conservation 10 ans, partitioning = archivage + performance requetes | Reutilisation integrity_journal_entries, JSONB dans documents | Table dediee = surface plus large mais requetes attestation independantes et archivage par annee |
Patterns reutilises
- PD-250 : Pattern
ALTER TYPE ADD VALUE IF NOT EXISTS hors transaction - PD-279 : Pattern down migration guard
COUNT(*) WHERE status = ... > 0 - PD-272 : Pattern trigger WORM
CREATE OR REPLACE FUNCTION + BEFORE UPDATE - PD-251/PD-237/PD-275 : Pattern
REVOKE TRUNCATE sur tables append-only - PD-272 : Pattern trigger append-only
BEFORE UPDATE OR DELETE pour attestations - PD-250 : Pattern
COMMENT ON pour documentation DDL
Hypotheses
| ID | Hypothese | Impact |
| H-07 | retention_due est un champ stocke, mis a jour par le retention orchestrator | La colonne est creee ici ; l'ecriture est hors perimetre PD-278 |
| H-09 | Attestation stockee dans table dediee avec partitioning | Table creee avec 2 partitions initiales (2025, 2026) |
Schema attestations (SS5.13)
vault_secure.dissemination_attestations (PARTITION BY RANGE issued_at)
├── id UUID PK (gen_random_uuid)
├── attestation_id UUID NOT NULL UNIQUE (per partition)
├── request_id UUID NOT NULL
├── document_ids UUID[] NOT NULL
├── actor_id UUID NOT NULL
├── issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
├── motif_communication VARCHAR(1024) NULL
├── dissemination_package_id UUID NULL
├── hash_evidence TEXT NOT NULL
├── signature_ref TEXT NULL (STUB: PD-37)
└── created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
Protections:
├── TRIGGER trg_dissemination_attestations_worm (BEFORE UPDATE OR DELETE → restrict_violation)
└── REVOKE TRUNCATE FROM PUBLIC
Note : PRIMARY KEY et UNIQUE incluent issued_at pour la compatibilite avec le partitioning PostgreSQL (la colonne de partition doit faire partie de la cle primaire).
Stubs documentes
| Stub | Story destination | Champ |
signature_ref | PD-37 — HSM signature probante | dissemination_attestations.signature_ref |
Matrice de couverture tests
| Test ID | Couverture par cette migration |
| TC-INV-01 | DIP dans enum_range (Phase 1) |
| TC-INV-05 | Table attestations avec partitioning + append-only (Phases 6, 8, 9) |
| TC-INV-06 | Trigger WORM motif_communication (Phase 4) |
| TC-INV-13 | CHECK constraint temporal order (Phase 3) |
| TC-NR-06 | Down migration bloquee si DIP actif |
| TC-NOM-06 | Trigger WORM bloque UPDATE motif (Phase 4) |
| TC-NOM-07 | Colonne dissemination_package_id nullable (Phase 2) |