v0.3.11-pre.007

This commit is contained in:
2026-09-08 11:32:41 +02:00
parent 355c5d1e9a
commit ed6c0ac10c
12 changed files with 1239 additions and 58 deletions

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/032-V0_3_11_RAW_TRANSACTION_INGEST_WORKER_FOUNDATION_PLAN.md -->
<!-- version: 6 -->
<!-- version: 7 -->
# Plan v0.3.11 — fondation runtime du Worker RawTransaction ingest
@@ -726,12 +726,14 @@ Budget cible : **1520 min**. Introduire le supervisor, les JoinSet/joins priv
Budget cible : **1520 min**. `mpsc` borné, backpressure, ingress privé, common `RawTransactionMaterial -> RawTransaction`, observation-key domain/golden et assembly. Pas de source réseau.
État après matérialisation : **implémenté, gate opérateur requis**. Le supervisor possède un channel central `tokio::sync::mpsc` borné par `admission_queue_capacity`. Le seam source reçoit directement un clone du sender borné ainsi que le `watch<bool>` de stop ; le harness prouve `send().await` sous saturation et un `select! biased` stop-before-send, sans helper de production mort avant les vraies sources. L'ingress reste crate-private et transporte material source-neutral, network attendu, provenance sûre et une source key opaque de 32 bytes. Le Worker canonicalise exclusivement via `ksp-raw-transaction-lib`, vérifie le réseau avant/après canonicalisation, dérive l'observation key sous `ksp.raw_transaction_ingest.observation.v1` à partir de `(network, signature, source_key)`, puis assemble un `RawTransactionAcquisition` via le common RAW. Au stop, le receiver ferme les nouvelles admissions puis canonicalise les entrées déjà admises avant le join final. Aucune persistence Store, source live, Transport, snapshot concret ou policy provider n'est introduite.
État après matérialisation : **implémenté et gate opérateur validé après `pre.006-fix.001`**. Le supervisor possède un channel central `tokio::sync::mpsc` borné par `admission_queue_capacity`. Le seam source reçoit directement un clone du sender borné ainsi que le `watch<bool>` de stop ; le harness prouve `send().await` sous saturation et un `select! biased` stop-before-send, sans helper de production mort avant les vraies sources. L'ingress reste crate-private et transporte material source-neutral, network attendu, provenance sûre et une source key opaque de 32 bytes. Le Worker canonicalise exclusivement via `ksp-raw-transaction-lib`, vérifie le réseau avant/après canonicalisation, dérive l'observation key sous `ksp.raw_transaction_ingest.observation.v1` à partir de `(network, signature, source_key)`, puis assemble un `RawTransactionAcquisition` via le common RAW. `pre.006-fix.001` corrige uniquement les séparateurs de branches du `tokio::select!` de test ; le gate communiqué le 8 septembre 2026 est ensuite vert sur `fmt`, audits, `check`, Clippy strict, 22 tests de crate et doc-tests. Aucun arbre Cargo n'a été rejoué après ce fix strictement sans changement de dépendance. Aucune persistence Store, source live, Transport, snapshot concret ou policy provider n'est introduite.
### `pre.007` — persistence Store + idempotence/conflict
Budget cible : **1520 min**. Port privé Store, mode Normal, concurrency bornée, outcomes new/idempotent/purged, observation distincte, Store error et content conflict terminal.
État après matérialisation : **implémenté, gate opérateur requis**. La crate possède désormais un port de persistence Store crate-private, implémenté par la façade `ksp-store-lib::Store`, et appelle exclusivement l'opération atomique transaction + observation en `RawTransactionAcquisitionMode::Normal`. Le supervisor possède un `JoinSet` privé de persistences ; il ne lit une nouvelle acquisition que si `in_flight < persistence_concurrency`, ce qui borne les écritures sans sémaphore ni file secondaire. Les succès distinguent `Inserted`, `AlreadyPresent`, `SkippedPurged` et les observations `Inserted`, `AlreadyPresent`, `NotRecorded`; `Rehydrated` ou toute combinaison impossible en mode Normal deviennent `runtime_invalid`. `store_api.raw_conflict` est réduit à `worker_raw_transaction_ingest.content_conflict`; toute autre erreur Store devient `worker_raw_transaction_ingest.store_failed` en ne conservant que le `ErrorCode` inférieur sûr. Le premier fault de cette tranche signale le stop privé, ferme ensuite les nouvelles admissions, draine les acquisitions déjà admises, rejoint persistences et sources, puis publie le terminal. Les races terminales fortes et le timeout/abort restent réservés à `pre.009`; les compteurs/snapshots restent réservés à `pre.008`.
### `pre.008` — snapshots concrets + projection Worker API
Budget cible : **1520 min**. `watch` latest-value, compteurs checked, common projection, slow/no listener et terminal retained.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/validation/028-V0_3_11_RAW_TRANSACTION_INGEST_WORKER_FOUNDATION.md -->
<!-- version: 9 -->
<!-- version: 10 -->
# Validation v0.3.11 — fondation runtime du Worker RawTransaction ingest
@@ -1014,3 +1014,162 @@ cargo tree -p ksp-worker-raw-transaction-ingest-lib -e features
Critère de passage : admission bornée/backpressure, stop-preemption, canonicalisation common, golden observation et assembly sont verts ; aucune persistence Store, source live, Transport ou snapshot concret n'apparaît.
## 18. Fermeture opérateur `pre.006-fix.001` et matérialisation `pre.007`
### 18.1 Fermeture opérateur de `pre.006-fix.001`
Le journal opérateur communiqué le 8 septembre 2026 ferme `0.3.11-pre.006-fix.001` (`workspace.package.version = 0.3.11-pre.6.fix.1`) :
```text
cargo fmt --all : terminé sans erreur
python3 scripts/audit_rust_workspace_rules.py : clean, export completeness 0
python3 scripts/audit_markdown_tables.py ... : clean (340 tables, 782 files)
cargo check --workspace : terminé sans erreur
cargo clippy --workspace --all-targets --all-features -- -D warnings : terminé sans erreur
cargo test -p ksp-worker-raw-transaction-ingest-lib : 15 unit + 3 dependency-boundary + 4 public API PASS, 0 échec
doc-tests : PASS
```
Aucun `cargo tree` n'a été rejoué après ce fix. Ce choix ne laisse pas de gap de dépendance : `pre.006-fix.001` modifie uniquement `Cargo.toml` pour le numéro de version, un test Rust et son delta ; aucune dépendance ni feature n'est ajoutée, retirée ou modifiée.
`pre.007` peut donc être ouverte.
### 18.2 Port Store privé et mode Normal
`pre.007` ajoute `RawTransactionIngestPersistencePort`, strictement crate-private. Son implémentation productive vise uniquement `ksp_store_lib::Store` et délègue à :
```text
RawTransactionWrite::persist_raw_transaction_acquisition(..., RawTransactionAcquisitionMode::Normal)
```
Le port ne connaît aucun backend concret. Il vérifie le network avant write et l'alignement exact entre transaction et observation. Le Store reste source de vérité ; aucun cache de déduplication mémoire n'est ajouté.
### 18.3 Classification entity + observation
Les succès Store admis en mode Normal sont classifiés exactement :
```text
Inserted + Inserted -> entity Inserted / observation Inserted
AlreadyPresent + Inserted -> entity AlreadyPresent / observation Inserted
AlreadyPresent + AlreadyPresent -> entity AlreadyPresent / observation AlreadyPresent
SkippedPurged + NotRecorded -> entity SkippedPurged / observation NotRecorded
```
`Rehydrated` est interdit en mode Normal. Toute combinaison non définie ci-dessus devient `worker_raw_transaction_ingest.runtime_invalid` et n'est jamais traitée comme un succès silencieux.
### 18.4 Content conflict et Store failure
Deux ErrorCode publics sont ouverts parce qu'ils deviennent réellement observables dans cette tranche :
```text
worker_raw_transaction_ingest.content_conflict
worker_raw_transaction_ingest.store_failed
```
Mapping :
```text
ksp_store_lib::ERROR_CODE_RAW_CONFLICT -> content_conflict
autre erreur Store -> store_failed
```
Pour `store_failed`, le Worker ne conserve que `store_domain` et `store_code`, issus du `ErrorCode` inférieur déjà sûr. Le message/source arbitraire Store n'est pas copié dans l'erreur Worker, le handle ou le terminal.
### 18.5 Concurrence bornée de persistence
Le supervisor possède un `JoinSet` privé de futures de persistence. Il n'exécute la branche admission que lorsque :
```text
persistence.len() < settings.persistence_concurrency()
```
Il n'existe ni sémaphore supplémentaire, ni queue de persistence non bornée. Sous saturation de persistence, le supervisor attend une complétion Store avant de retirer une nouvelle acquisition de la queue centrale ; la backpressure remonte ainsi jusqu'au `mpsc` borné.
Le harness avec `persistence_concurrency = 2` bloque volontairement les écritures Store et prouve que `max_active == 2` avec quatre acquisitions admises.
### 18.6 Drain et terminal de `pre.007`
Lors d'un stop normal, d'un content conflict ou d'une erreur Store :
```text
signaler le stop privé aux sources si fault
fermer les nouvelles admissions
canonicaliser/persister les acquisitions déjà admises
attendre toutes les persistences en vol
attendre toutes les tâches source
publier Stopped ou le premier Faulted retenu
```
Cette tranche ne fige pas encore la priorité complète des courses stop/fault et n'introduit aucun timeout/abort ; ces responsabilités restent dans `pre.009`.
### 18.7 Preuves ajoutées
Les tests `pre.007` couvrent :
```text
mode Store exactement Normal
Inserted / AlreadyPresent / SkippedPurged
observation Inserted / AlreadyPresent / NotRecorded
Rehydrated et couples impossibles -> runtime_invalid
network mismatch avant write
content conflict -> ErrorCode Worker terminal dédié
Store failure -> ErrorCode Worker dédié sans texte distant
persistence_concurrency réellement bornée à 2
content conflict runtime -> terminal Faulted(content_conflict)
Store failure runtime -> terminal Faulted(store_failed)
public API -> deux ErrorCode stables depuis crate-root
dependency boundary -> persistence privée requise, backend/Transport/snapshot interdits
```
### 18.8 Frontière volontaire de `pre.007`
La tranche n'introduit toujours aucun :
```text
backend Store direct
ForceRehydrate
source live
Transport
Config
snapshot concret / compteurs publics
source_failed
counter_exhausted
drain_timeout / abort forcé
retry / reconnect / gap repair
```
`pre.008` reste propriétaire des snapshots/counters. `pre.009` reste propriétaire du hardening shutdown, du timeout/abort et des courses fault/stop.
### 18.9 Preuves locales d'assemblage `pre.007`
Exécuté dans l'environnement d'assemblage :
```text
python3 scripts/audit_rust_workspace_rules.py
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
KSP workspace Rust rule audit: clean
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas
Markdown table audit: clean
scan production : aucun unwrap, expect, panic, unbounded_channel
scan scope : aucun ksp-store-postgres-lib, aucun Transport, aucun snapshot concret
```
L'environnement d'assemblage ne fournit ni `cargo`, ni `rustc`, ni `rustfmt`. Aucun gate Cargo de `pre.007` n'est déclaré PASS localement.
### 18.10 Gate opérateur demandé pour `pre.007`
Aucune dépendance ni feature n'étant modifiée par `pre.007`, aucun `cargo tree` intermédiaire n'est requis. Le gate demandé est :
```bash
cargo fmt --all
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-worker-raw-transaction-ingest-lib
```
Critère de passage : persistence Normal, outcomes/idempotence/purge, concurrence bornée et terminaux conflict/Store sont verts, sans backend direct, Transport ni snapshot concret.