# Delta `0.3.16-pre.008` — hardening technique de la vertical slice variantes/conflits ## Base requise ```text 0.3.16-pre.007-fix.003 appliqué workspace.package.version = 0.3.16-pre.7.fix.3 ``` Le gate opérateur de `pre.007-fix.003` est entièrement propre : format, audits Rust/Markdown, `cargo check --workspace`, Clippy workspace/all-targets/all-features, Store API, Store façade, PostgreSQL, Job Backfill et l'intégralité des suites ciblées du Worker passent. `pre.007` est donc fermé avant ouverture de cette tranche. ## Objectif `pre.008` est la tranche de hardening technique finale de la vertical slice `0.3.16` : ```text concurrence même identité rollback transactionnel promotion/conflit cohérence projection V001 / selector V003 conservation du canonique en conflit régressions Job Backfill / Worker preuves live PostgreSQL opt-in actualisées gate workspace ``` Aucune nouvelle capacité fonctionnelle, résolution opérateur, retry Store, reconnexion Transport ou Store Desk n'est introduite. ## Version ```text workspace.package.version = 0.3.16-pre.8 ``` ## Hardening PostgreSQL statique Les canaris V003 verrouillent désormais explicitement deux invariants supplémentaires : 1. la branche `ActiveConflict` persiste/réutilise la variante entrante et ouvre/complète le conflict case, mais n'appelle jamais la promotion canonique et ne touche ni `UPDATE_CANONICAL_TRANSACTION_PROJECTION_SQL` ni `UPDATE_TRANSACTION_VARIANT_SELECTOR_SQL` ; 2. les helpers de promotion et de conflict case reçoivent la transaction PostgreSQL de l'acquisition et ne peuvent jamais effectuer leur propre `COMMIT`. Les gardes existantes restent exigées : ```text identity row -> FOR UPDATE selector promotion -> expected canonical_variant_id + expected canonical_revision conflict update -> expected revision ``` Ainsi, selector, projection V001, ledger V003, conflict case, observation et mapping restent bornés par la transaction d'acquisition externe. ## Preuve live PostgreSQL actualisée Le test opt-in `postgres_raw_transaction_live.rs` est réaligné sur la sémantique `pre.007`. Le scénario de divergence concurrente d'une même identité attend désormais : ```text 1 x InsertedCanonical 1 x QuarantinedConflict 2 variantes durables 2 observations durables 2 mappings observation -> variante 1 selector canonique 1 conflict case Open revision 1 projection V001 == variante désignée par le selector ``` L'ordre d'arrivée n'est pas supposé : l'une ou l'autre acquisition peut devenir le premier canonique, mais la seconde doit être quarantinée sans écraser ce canonique. ### Rollback après ouverture réelle d'un conflit Un nouveau scénario live force une erreur après les écritures de divergence mais avant le commit : ```text canonique existant -> incoming divergent -> variante entrante créée -> conflict case créé -> collision volontaire d'observation avec une autre identité -> erreur avant COMMIT -> rollback implicite de la transaction ``` Après l'échec, la preuve exige exactement l'état antérieur : ```text 1 seule variante pour l'identité 1 selector 0 conflict case 1 observation de seed 1 mapping de seed projection V001 toujours cohérente avec le selector ``` Cela couvre le risque qu'un conflict case ou une variante orpheline survive à un échec tardif de l'acquisition. Le test live reste `#[ignore]`, lit uniquement un URI PostgreSQL dédié depuis stdin, refuse un schéma KSP préexistant et ne devient pas un gate automatique sans ressource opérateur explicite. ## Régression Job Backfill Le Job Backfill ajoute un canari sur une réobservation déjà idempotente d'un conflit durable : ```text entity = AlreadyPresent observation = AlreadyPresent variant = QuarantinedConflict ``` La projection doit rester : ```text BackfillEntityPersistence::Conflict BackfillObservationPersistence::AlreadyPresent ``` Le conflit durable n'est donc jamais aplati en simple `AlreadyPresent`, même lorsque l'observation est elle-même idempotente. ## Régression Worker Le Worker ajoute une preuve runtime avec deux identités successives alors que le Store retourne `QuarantinedConflict` : ```text première identité -> QuarantinedConflict seconde identité -> QuarantinedConflict ``` Les deux acquisitions doivent atteindre le Store. Après les deux résultats : ```text state = Running health = Degraded persisted_total = 2 content_conflict_total = 2 store_failure_total = 0 ``` Le Stop explicite doit ensuite terminer en `Stopped`. Cette preuve verrouille directement l'invariant « un conflit durable n'empêche pas la progression d'une autre identité ». ## Recalibrage du lifecycle de fermeture Le plan 038 est ajusté pour respecter les règles `VER-LIFECYCLE` actuelles, qui sont plus strictes que le découpage historique `pre.008 -> rel.001` : ```text pre.008 = hardening / gate technique pre.009 = réconciliation documentaire finale pre.010 = préparation minimale de publication rel.001 = mécanique de publication stable ``` `pre.008` ne modifie donc ni `CHANGELOG.md`, ni `ROADMAP.md`, ni le prompt de démarrage suivant, et ne fait pas la réconciliation README/USAGE finale. ## Fichiers modifiés ```text Cargo.toml crates/ksp-job-backfill-lib/unit_tests/persistence.rs crates/ksp-store-postgres-lib/tests/hardening_completeness.rs crates/ksp-store-postgres-lib/tests/postgres_raw_transaction_live.rs crates/ksp-store-postgres-lib/tests/v003_variant_persistence.rs crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime.rs docs/plans/038-V0_3_16_RAW_RESILIENCE_CONFLICT_PLAN.md ``` ## Fichier ajouté ```text deltas/0.3.16/pre.008.md ``` ## Fichiers supprimés ```text aucun ``` ## Validations exécutées lors de la génération ```bash python3 scripts/audit_rust_workspace_rules.py python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas ``` Le toolchain Rust n'est pas installé dans l'environnement de génération. `cargo fmt`, `cargo check`, Clippy et les tests Cargo ne sont donc pas déclarés exécutés ici. La preuve PostgreSQL live reste volontairement opt-in et n'est pas déclarée exécutée sans URI isolé fourni par l'opérateur. ## Gate opérateur demandé ```bash cargo fmt --all cargo fmt --all -- --check python3 scripts/audit_rust_workspace_rules.py python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas cargo check --workspace cargo clippy --workspace --all-targets --all-features -- -D warnings cargo test -p ksp-store-api --all-targets --all-features cargo test -p ksp-store-lib --all-targets --all-features cargo test -p ksp-store-postgres-lib --all-targets --all-features cargo test -p ksp-job-backfill-lib --all-targets --all-features cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features ``` Le live PostgreSQL peut être exécuté séparément sur une base dédiée vide : ```bash cargo test -p ksp-store-postgres-lib --test postgres_raw_transaction_live -- --ignored --nocapture ``` Il ne doit jamais être lancé contre une base KSP contenant des données utiles. ## Décisions prises - aucune modification runtime n'est nécessaire pour fermer le hardening observé ; - le conflit durable concurrent est un succès quarantiné, pas une erreur backend ; - les deux observations concurrentes divergentes doivent être conservées ; - un échec tardif avant commit doit annuler variante et conflict case nouvellement créés ; - le canonique ne change jamais dans la branche de conflit ; - Backfill conserve explicitement la distinction conflit / observation idempotente ; - le Worker doit continuer à traiter les identités suivantes après une quarantaine ; - le lifecycle final est séparé en `pre.008`, `pre.009`, `pre.010`, puis `rel.001` conformément aux règles actuelles. ## Questions ouvertes Aucune question fonctionnelle bloquante pour `0.3.16`. Le test PostgreSQL live reste conditionné à la disponibilité d'un PostgreSQL isolé ; son absence n'autorise pas à prétendre qu'il a été exécuté. ## Suite Après gate technique propre : `0.3.16-pre.009` — réconciliation documentaire finale de `0.3.16`, sans nouveau comportement runtime.