8.2 KiB
Delta 0.3.16-pre.008 — hardening technique de la vertical slice variantes/conflits
Base requise
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 :
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
workspace.package.version = 0.3.16-pre.8
Hardening PostgreSQL statique
Les canaris V003 verrouillent désormais explicitement deux invariants supplémentaires :
- la branche
ActiveConflictpersiste/réutilise la variante entrante et ouvre/complète le conflict case, mais n'appelle jamais la promotion canonique et ne touche niUPDATE_CANONICAL_TRANSACTION_PROJECTION_SQLniUPDATE_TRANSACTION_VARIANT_SELECTOR_SQL; - 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 :
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 :
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 :
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 :
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 :
entity = AlreadyPresent
observation = AlreadyPresent
variant = QuarantinedConflict
La projection doit rester :
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 :
première identité -> QuarantinedConflict
seconde identité -> QuarantinedConflict
Les deux acquisitions doivent atteindre le Store. Après les deux résultats :
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 :
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
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é
deltas/0.3.16/pre.008.md
Fichiers supprimés
aucun
Validations exécutées lors de la génération
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é
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 :
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, puisrel.001conformé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.