0.3.15-pre.014-fix.001

This commit is contained in:
2026-09-17 23:09:21 +02:00
parent ad24cb36fa
commit 6d820bf0d7
14 changed files with 648 additions and 67 deletions

View File

@@ -1,12 +1,12 @@
# file: Cargo.toml # file: Cargo.toml
# version: 628 # version: 629
[workspace] [workspace]
resolver = "3" resolver = "3"
members = ["crates/ksp-app-backfill-desk", "crates/ksp-app-config-desk", "crates/ksp-app-raw-transaction-ingest-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-store-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-interface-lib", "crates/ksp-job-api", "crates/ksp-job-backfill-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-program-api", "crates/ksp-raw-transaction-lib", "crates/ksp-store-api", "crates/ksp-store-lib", "crates/ksp-store-postgres-lib", "crates/ksp-wallet-lib", "crates/ksp-worker-api", "crates/ksp-worker-raw-transaction-ingest-lib"] members = ["crates/ksp-app-backfill-desk", "crates/ksp-app-config-desk", "crates/ksp-app-raw-transaction-ingest-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-store-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-interface-lib", "crates/ksp-job-api", "crates/ksp-job-backfill-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-program-api", "crates/ksp-raw-transaction-lib", "crates/ksp-store-api", "crates/ksp-store-lib", "crates/ksp-store-postgres-lib", "crates/ksp-wallet-lib", "crates/ksp-worker-api", "crates/ksp-worker-raw-transaction-ingest-lib"]
[workspace.package] [workspace.package]
version = "0.3.15-pre.14" version = "0.3.15-pre.14.fix.1"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project" repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"

View File

@@ -1,5 +1,5 @@
<!-- file: ROADMAP.md --> <!-- file: ROADMAP.md -->
<!-- version: 112 --> <!-- version: 113 -->
# Roadmap KSP # Roadmap KSP
@@ -108,7 +108,9 @@ RAW -> STRUCTURAL -> DECODED -> DOMAIN
- [X] `0.3.13` — Convergence live multi-source du Worker RAW livrée : composition caller-owned `1..32` sources dun même réseau, cinq familles Yellowstone / Standard Logs / Standard Block / Helius Transaction / HTTP Block Polling, hydration globale des trois voies reference-bearing, RAW-direct Legacy/V0/V1 pour les deux voies blocs, observations multiples, content conflict explicite, coalescence cross-source, fairness/backpressure/health source-neutral et shutdown hardening sans second actor Transport. Gate workspace complet vert (`1 850` PASS / `0` échec / `15` ignored) et smokes keyless HTTP + WebSocket Devnet verts ; les preuves provider/resource-gated non exécutées restent explicitement non revendiquées. - [X] `0.3.13` — Convergence live multi-source du Worker RAW livrée : composition caller-owned `1..32` sources dun même réseau, cinq familles Yellowstone / Standard Logs / Standard Block / Helius Transaction / HTTP Block Polling, hydration globale des trois voies reference-bearing, RAW-direct Legacy/V0/V1 pour les deux voies blocs, observations multiples, content conflict explicite, coalescence cross-source, fairness/backpressure/health source-neutral et shutdown hardening sans second actor Transport. Gate workspace complet vert (`1 850` PASS / `0` échec / `15` ignored) et smokes keyless HTTP + WebSocket Devnet verts ; les preuves provider/resource-gated non exécutées restent explicitement non revendiquées.
- [X] `0.3.14` — Gap repair/hardening multi-source du Worker RAW livré : gaps run-local bornés, distinction reconnect/replay/delivery/coverage, TargetCoverage conservative, coverage redondante prouvée, discovery HTTP bornée, known-reference hydration, réconciliation source-loss, health gap-aware, fairness nominal/repair, snapshots publics source-neutral et shutdown/drain renforcé. Le pipeline Common RAW/Store reste unique, Worker/Backfill restent indépendants, EARLY nest pas ajouté. Gate workspace complet vert (`1 931` PASS / `0` échec / `15` ignored) et smokes keyless HTTP + WebSocket Devnet verts ; preuves provider/resource-gated non provisionnées explicitement non revendiquées. - [X] `0.3.14` — Gap repair/hardening multi-source du Worker RAW livré : gaps run-local bornés, distinction reconnect/replay/delivery/coverage, TargetCoverage conservative, coverage redondante prouvée, discovery HTTP bornée, known-reference hydration, réconciliation source-loss, health gap-aware, fairness nominal/repair, snapshots publics source-neutral et shutdown/drain renforcé. Le pipeline Common RAW/Store reste unique, Worker/Backfill restent indépendants, EARLY nest pas ajouté. Gate workspace complet vert (`1 931` PASS / `0` échec / `15` ignored) et smokes keyless HTTP + WebSocket Devnet verts ; preuves provider/resource-gated non provisionnées explicitement non revendiquées.
- [ ] `0.3.15` — Introduire `ksp-app-raw-transaction-ingest-desk`, Desk Tauri KSP de composition/supervision des routes live `RawTransaction`. Une route est une stratégie complète pouvant nécessiter plusieurs capabilities/endpoints (par exemple gRPC + HTTP ou WS + HTTP) et nest sélectionnable que si toutes ses requirements sont composables depuis Config sur le réseau choisi ; la présence dun provider ne vaut jamais capability implicite. Au Start, la Config est revalidée et **une instance Worker est lancée par route sélectionnée** — pas par source physique — afin de valider lopérabilité réelle puis exposer lifecycle, health, backpressure, reconnect/replay, gaps/repair et compteurs sûrs. Plusieurs Workers peuvent partager le même Store, sans coordination implicite de gaps entre routes. La Desk ne réimplémente ni Transport, discovery, hydration, déduplication, repair ni persistence. - [ ] `0.3.15` — Introduire `ksp-app-raw-transaction-ingest-desk`, Desk Tauri KSP de composition/supervision des routes live `RawTransaction`. Une route est une stratégie complète pouvant nécessiter plusieurs capabilities/endpoints (par exemple gRPC + HTTP ou WS + HTTP) et nest sélectionnable que si toutes ses requirements sont composables depuis Config sur le réseau choisi ; la présence dun provider ne vaut jamais capability implicite. Au Start, la Config est revalidée et **une instance Worker est lancée par route sélectionnée** — pas par source physique — afin de valider lopérabilité réelle puis exposer lifecycle, health, backpressure, reconnect/replay, gaps/repair et compteurs sûrs. Plusieurs Workers peuvent partager le même Store, sans coordination implicite de gaps entre routes. La Desk ne réimplémente ni Transport, discovery, hydration, déduplication, repair ni persistence.
- [ ] `0.3.16`Étendre `ksp-job-backfill-lib` et `ksp-app-backfill-desk` au **backfill multi-source/multi-stratégie** à partir de la même matrice d'acquisition auditée en `0.3.9`. Conserver la stratégie actuelle `getSignaturesForAddress + getTransaction` comme première voie HTTP valide, puis ajouter seulement les voies historiques/catch-up/gap-repair réellement pertinentes et sûres, sans supposer qu'une source live WS constitue un historique universel. - [ ] `0.3.16`**RAW resilience / conflict management** : faire converger Store API/façade/PostgreSQL, Worker live et Store Desk autour de variantes RAW durables, résolution automatique `Exact / CompatibleLessComplete / CompatibleMoreComplete / Conflict`, quarantaine des conflits non résolus sans arrêt du Worker, historique réversible des promotions/résolutions, retry Store sans perte silencieuse et reconnexion Transport configurable/bornée. Une provenance provider n'est jamais une priorité canonique en soi ; la représentation prouvée la plus complète gagne.
- [ ] `0.3.17` — Étendre `ksp-job-backfill-lib` au **backfill multi-route/multi-stratégie** à partir de la matrice d'acquisition auditée en `0.3.9` et de la convergence Store `0.3.16`. Conserver `getSignaturesForAddress + getTransaction` comme première voie valide puis ajouter les stratégies historiques/catch-up réellement pertinentes sans edge Job ↔ Worker.
- [ ] `0.3.18` — Adapter `ksp-app-backfill-desk` au Job multi-route `0.3.17` : inventaire/composition de routes, sélection/supervision et progression sur le modèle structurel de `ksp-app-raw-transaction-ingest-desk`, sans réimplémenter la logique du Job.
### TODO/IDEAS — applications spécialisées et control plane ### TODO/IDEAS — applications spécialisées et control plane
@@ -118,7 +120,7 @@ RAW -> STRUCTURAL -> DECODED -> DOMAIN
### TODO/IDEAS — taxonomie D1, processing et rétention ### TODO/IDEAS — taxonomie D1, processing et rétention
- [ ] **TODO** — maintenir la matrice dadmission HTTP/WS/gRPC/provider lors de toute nouvelle famille D1 : plusieurs sources ne convergent vers un même struct que si elles satisfont la même sémantique sans perte. - [ ] **TODO** — maintenir la matrice dadmission HTTP/WS/gRPC/provider lors de toute nouvelle famille D1 : plusieurs sources ne convergent vers un même struct que si elles satisfont la même sémantique sans perte.
- [X] **TODO `0.3.9`** — matrice RAW Transaction unique produite et consolidée dans `docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md`, avec séparation possibilités/support/preuve, capabilities orthogonales aux producteurs, provenance/déduplication/continuité et handoffs désormais scindés `0.3.11``0.3.14` Worker live / `0.3.16` Backfill historique. - [X] **TODO `0.3.9`** — matrice RAW Transaction unique produite et consolidée dans `docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md`, avec séparation possibilités/support/preuve, capabilities orthogonales aux producteurs, provenance/déduplication/continuité et handoffs désormais scindés `0.3.11``0.3.14` Worker live / `0.3.16` résilience RAW / `0.3.17``0.3.18` Backfill multi-route.
- [X] **TODO Helius `0.3.13`** — la voie Worker Helius `transactionSubscribe` réutilise la surface Transport et les profils Config Helius déjà existants avec `KSP_SECRET_HELIUS_API_KEY`; aucun nouveau profil, secret, SDK provider, tier codé ou second client parallèle na été nécessaire. Le payload riche reste Transport-owned et la voie Worker hydrate via `getTransaction observed` tant que le RAW complet nest pas prouvé directement. - [X] **TODO Helius `0.3.13`** — la voie Worker Helius `transactionSubscribe` réutilise la surface Transport et les profils Config Helius déjà existants avec `KSP_SECRET_HELIUS_API_KEY`; aucun nouveau profil, secret, SDK provider, tier codé ou second client parallèle na été nécessaire. Le payload riche reste Transport-owned et la voie Worker hydrate via `getTransaction observed` tant que le RAW complet nest pas prouvé directement.
- [X] **TODO réseau** — identité KSP canonique fixée à `mainnet` dans Config/Store/Transport/tests ; `mainnet-beta` reste uniquement un alias legacy/externe lorsque la frontière provider l'exige. Les données D1 RAW antérieures restent non autoritaires pendant cette phase et aucune compatibilité de base de test n'impose l'ancien libellé. - [X] **TODO réseau** — identité KSP canonique fixée à `mainnet` dans Config/Store/Transport/tests ; `mainnet-beta` reste uniquement un alias legacy/externe lorsque la frontière provider l'exige. Les données D1 RAW antérieures restent non autoritaires pendant cette phase et aucune compatibilité de base de test n'impose l'ancien libellé.
- [X] `RawAccountState` + observation — contrat commun stabilisé en `0.3.1` avec bytes complets + slot, provenance séparée et enrichissements source-specific optionnels ; la persistence PostgreSQL physique est complétée en `0.3.4` avec les quatre capabilities account et la conformance RAW 10/10. - [X] `RawAccountState` + observation — contrat commun stabilisé en `0.3.1` avec bytes complets + slot, provenance séparée et enrichissements source-specific optionnels ; la persistence PostgreSQL physique est complétée en `0.3.4` avec les quatre capabilities account et la conformance RAW 10/10.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-store-postgres-lib/src/raw_transaction.rs // file: crates/ksp-store-postgres-lib/src/raw_transaction.rs
// version: 9 // version: 10
pub(crate) mod cursor; pub(crate) mod cursor;
@@ -691,7 +691,7 @@ pub(crate) async fn persist_raw_transaction_acquisition(
}, },
}; };
match comparison { match comparison {
ExistingTransactionMatch::Active => ksp_store_api::RawEntityWriteOutcome::AlreadyPresent, ExistingTransactionMatch::Active | ExistingTransactionMatch::ActiveIncomingTruncatedLogs => ksp_store_api::RawEntityWriteOutcome::AlreadyPresent,
ExistingTransactionMatch::Purged => { ExistingTransactionMatch::Purged => {
if mode == ksp_store_api::RawTransactionAcquisitionMode::ForceRehydrate { if mode == ksp_store_api::RawTransactionAcquisitionMode::ForceRehydrate {
let rehydrate_result = rehydrate_transaction(&sql_transaction, &raw_transaction).await; let rehydrate_result = rehydrate_transaction(&sql_transaction, &raw_transaction).await;
@@ -853,6 +853,7 @@ pub(crate) async fn transition_raw_transaction_retention(
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum ExistingTransactionMatch { enum ExistingTransactionMatch {
Active, Active,
ActiveIncomingTruncatedLogs,
Purged, Purged,
} }
@@ -1143,10 +1144,86 @@ fn compare_existing_transaction(
if raw_transactions_equal(&stored, incoming) { if raw_transactions_equal(&stored, incoming) {
return std::result::Result::Ok(ExistingTransactionMatch::Active); return std::result::Result::Ok(ExistingTransactionMatch::Active);
} }
if raw_transaction_incoming_truncated_log_messages_compatible(&stored, incoming) {
log_raw_transaction_compatible_truncated_log_messages(network, &stored, incoming);
return std::result::Result::Ok(ExistingTransactionMatch::ActiveIncomingTruncatedLogs);
}
log_raw_transaction_content_conflict(network, &stored, incoming); log_raw_transaction_content_conflict(network, &stored, incoming);
return std::result::Result::Err(conflict("raw_acquisition_content_conflict")); return std::result::Result::Err(conflict("raw_acquisition_content_conflict"));
} }
fn raw_transaction_incoming_truncated_log_messages_compatible(stored: &ksp_store_api::RawTransaction, incoming: &ksp_store_api::RawTransaction) -> bool {
if stored.reference() != incoming.reference()
|| stored.slot() != incoming.slot()
|| stored.block_time() != incoming.block_time()
|| stored.payload().format_id() != incoming.payload().format_id()
|| stored.payload().format_version() != incoming.payload().format_version()
{
return false;
}
let stored_payload = serde_json::from_slice::<serde_json::Value>(stored.payload().bytes());
let incoming_payload = serde_json::from_slice::<serde_json::Value>(incoming.payload().bytes());
let (stored_payload, incoming_payload) = match (stored_payload, incoming_payload) {
(std::result::Result::Ok(serde_json::Value::Object(stored)), std::result::Result::Ok(serde_json::Value::Object(incoming))) => (stored, incoming),
_ => return false,
};
if stored_payload.get("transaction") != incoming_payload.get("transaction")
|| stored_payload.get("version") != incoming_payload.get("version")
|| stored_payload.get("transactionIndex") != incoming_payload.get("transactionIndex")
|| json_object_other_fields_mismatch(&stored_payload, &incoming_payload, RAW_TRANSACTION_CONTENT_CONFLICT_PAYLOAD_FIELDS.as_slice())
{
return false;
}
let (stored_meta, incoming_meta) = match (stored_payload.get("meta"), incoming_payload.get("meta")) {
(std::option::Option::Some(serde_json::Value::Object(stored)), std::option::Option::Some(serde_json::Value::Object(incoming))) => (stored, incoming),
_ => return false,
};
if raw_meta_other_than_log_messages_mismatch(stored_meta, incoming_meta) {
return false;
}
let (stored_logs, incoming_logs) = match (stored_meta.get("logMessages"), incoming_meta.get("logMessages")) {
(std::option::Option::Some(serde_json::Value::Array(stored)), std::option::Option::Some(serde_json::Value::Array(incoming))) => {
(stored.as_slice(), incoming.as_slice())
},
_ => return false,
};
return raw_log_messages_incoming_truncated_compatible(stored_logs, incoming_logs);
}
fn raw_meta_other_than_log_messages_mismatch(
stored: &serde_json::Map<std::string::String, serde_json::Value>,
incoming: &serde_json::Map<std::string::String, serde_json::Value>,
) -> bool {
let stored_without_logs = stored.iter().filter(|(key, _)| return key.as_str() != "logMessages").collect::<std::collections::BTreeMap<_, _>>();
let incoming_without_logs = incoming.iter().filter(|(key, _)| return key.as_str() != "logMessages").collect::<std::collections::BTreeMap<_, _>>();
return stored_without_logs != incoming_without_logs;
}
fn raw_log_messages_incoming_truncated_compatible(stored: &[serde_json::Value], incoming: &[serde_json::Value]) -> bool {
if raw_log_messages_exact_truncation_marker_count(stored) != 0 {
return false;
}
let marker_index = match incoming.iter().position(raw_log_message_is_exact_truncation_marker) {
std::option::Option::Some(value) => value,
std::option::Option::None => return false,
};
if raw_log_messages_exact_truncation_marker_count(incoming) != 1 || marker_index >= stored.len() {
return false;
}
if incoming[..marker_index] != stored[..marker_index] {
return false;
}
return true;
}
fn raw_log_messages_exact_truncation_marker_count(values: &[serde_json::Value]) -> usize {
return values.iter().filter(|value| return raw_log_message_is_exact_truncation_marker(value)).count();
}
fn raw_log_message_is_exact_truncation_marker(value: &serde_json::Value) -> bool {
return matches!(value, serde_json::Value::String(line) if line == "Log truncated");
}
fn raw_transaction_content_conflict_diagnostic( fn raw_transaction_content_conflict_diagnostic(
stored: &ksp_store_api::RawTransaction, stored: &ksp_store_api::RawTransaction,
incoming: &ksp_store_api::RawTransaction, incoming: &ksp_store_api::RawTransaction,
@@ -1374,6 +1451,25 @@ fn json_object_other_fields_mismatch(
return stored_other != incoming_other; return stored_other != incoming_other;
} }
fn log_raw_transaction_compatible_truncated_log_messages(
network: &ksp_store_api::RawNetworkId,
stored: &ksp_store_api::RawTransaction,
incoming: &ksp_store_api::RawTransaction,
) {
let diagnostic = raw_transaction_content_conflict_diagnostic(stored, incoming);
ksp_logging_lib::debug!(
target: crate::TRACING_TARGET,
domain = "store.raw_transaction.compatible_partial",
network = network.as_str(),
slot = stored.slot(),
stored_log_messages_count = diagnostic.log_messages.stored_count,
incoming_log_messages_count = diagnostic.log_messages.incoming_count,
incoming_log_messages_has_truncation_marker = diagnostic.log_messages.incoming_has_truncation_marker,
"PostgreSQL Store accepted compatible truncated RAW transaction logs without replacing canonical content"
);
return;
}
fn log_raw_transaction_content_conflict( fn log_raw_transaction_content_conflict(
network: &ksp_store_api::RawNetworkId, network: &ksp_store_api::RawNetworkId,
stored: &ksp_store_api::RawTransaction, stored: &ksp_store_api::RawTransaction,

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-store-postgres-lib/tests/hardening_completeness.rs // file: crates/ksp-store-postgres-lib/tests/hardening_completeness.rs
// version: 25 // version: 26
#![warn(missing_docs)] #![warn(missing_docs)]
#![deny(unreachable_pub)] #![deny(unreachable_pub)]
@@ -643,3 +643,23 @@ fn v0_3_15_pre_014_content_conflict_diagnostic_exposes_only_block_slot_and_bound
} }
return; return;
} }
#[test]
fn v0_3_15_pre_014_fix_001_truncated_log_compatibility_is_narrow_and_keeps_canonical_content() {
let source = include_str!("../src/raw_transaction.rs");
for required in [
"ActiveIncomingTruncatedLogs",
"raw_transaction_incoming_truncated_log_messages_compatible",
"raw_meta_other_than_log_messages_mismatch",
"raw_log_messages_incoming_truncated_compatible",
"raw_log_message_is_exact_truncation_marker",
"marker_index >= stored.len()",
"incoming[..marker_index] != stored[..marker_index]",
"PostgreSQL Store accepted compatible truncated RAW transaction logs without replacing canonical content",
] {
assert!(source.contains(required), "missing narrow truncated-log compatibility guard: {required}");
}
assert!(!source.contains("UPDATE ksp_raw_transactions SET payload"));
assert!(!source.contains("DELETE FROM ksp_raw_transactions"));
return;
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-store-postgres-lib/unit_tests/raw_transaction.rs // file: crates/ksp-store-postgres-lib/unit_tests/raw_transaction.rs
// version: 8 // version: 9
fn network() -> ksp_store_api::RawNetworkId { fn network() -> ksp_store_api::RawNetworkId {
return match ksp_store_api::RawNetworkId::new("devnet") { return match ksp_store_api::RawNetworkId::new("devnet") {
@@ -457,6 +457,72 @@ fn v0_3_15_pre_014_log_messages_conflict_diagnostic_distinguishes_prefix_truncat
return; return;
} }
#[test]
fn v0_3_15_pre_014_fix_001_incoming_truncated_logs_are_compatible_only_when_missing_middle_is_proven() {
let stored = raw_transaction(
9,
br#"{"transaction":["AQID","base64"],"meta":{"err":null,"fee":5000,"logMessages":["Program Vote111 invoke [1]","Program log: stable","Program log: detail-a","Program log: detail-b","Program Vote111 success"],"rewards":[]},"version":0,"transactionIndex":7}"#,
7,
);
let incoming = raw_transaction(
9,
br#"{"transaction":["AQID","base64"],"meta":{"err":null,"fee":5000,"logMessages":["Program Vote111 invoke [1]","Program log: stable","Log truncated","Program Vote111 success"],"rewards":[]},"version":0,"transactionIndex":7}"#,
8,
);
assert!(super::raw_transaction_incoming_truncated_log_messages_compatible(&stored, &incoming));
let mut stored_row = transaction_row("full");
stored_row.signature = stored.reference().signature().as_bytes().to_vec();
stored_row.slot_text = stored.slot().to_string();
stored_row.block_time_unix_millis = stored.block_time().map(|value| return value.unix_millis());
stored_row.format_id = stored.payload().format_id().as_str().to_owned();
stored_row.format_version = i64::from(stored.payload().format_version());
stored_row.content_hash = stored.payload().content_hash().as_bytes().to_vec();
stored_row.payload = std::option::Option::Some(stored.payload().bytes().to_vec());
let comparison = super::compare_existing_transaction(&network(), stored_row, &incoming);
assert!(matches!(comparison, std::result::Result::Ok(super::ExistingTransactionMatch::ActiveIncomingTruncatedLogs)));
return;
}
#[test]
fn v0_3_15_pre_014_fix_001_truncated_log_compatibility_rejects_unproven_or_reverse_changes() {
let stored = raw_transaction(
9,
br#"{"transaction":["AQID","base64"],"meta":{"err":null,"fee":5000,"logMessages":["Program Vote111 invoke [1]","Program log: stable","Program log: detail-a","Program log: detail-b","Program Vote111 success"],"rewards":[]},"version":0,"transactionIndex":7}"#,
7,
);
let bad_prefix = raw_transaction(
9,
br#"{"transaction":["AQID","base64"],"meta":{"err":null,"fee":5000,"logMessages":["Program Vote111 invoke [1]","Program log: changed","Log truncated","Program Vote111 success"],"rewards":[]},"version":0,"transactionIndex":7}"#,
8,
);
assert!(!super::raw_transaction_incoming_truncated_log_messages_compatible(&stored, &bad_prefix));
let post_marker_material = raw_transaction(
9,
br#"{"transaction":["AQID","base64"],"meta":{"err":null,"fee":5000,"logMessages":["Program Vote111 invoke [1]","Program log: stable","Log truncated","Program Other111 success"],"rewards":[]},"version":0,"transactionIndex":7}"#,
8,
);
assert!(super::raw_transaction_incoming_truncated_log_messages_compatible(&stored, &post_marker_material));
let other_meta = raw_transaction(
9,
br#"{"transaction":["AQID","base64"],"meta":{"err":null,"fee":5001,"logMessages":["Program Vote111 invoke [1]","Program log: stable","Log truncated","Program Vote111 success"],"rewards":[]},"version":0,"transactionIndex":7}"#,
8,
);
assert!(!super::raw_transaction_incoming_truncated_log_messages_compatible(&stored, &other_meta));
let non_exact_marker = raw_transaction(
9,
br#"{"transaction":["AQID","base64"],"meta":{"err":null,"fee":5000,"logMessages":["Program Vote111 invoke [1]","Program log: stable","Log truncated by provider"],"rewards":[]},"version":0,"transactionIndex":7}"#,
8,
);
assert!(!super::raw_transaction_incoming_truncated_log_messages_compatible(&stored, &non_exact_marker));
let stored_truncated = raw_transaction(
9,
br#"{"transaction":["AQID","base64"],"meta":{"err":null,"fee":5000,"logMessages":["Program Vote111 invoke [1]","Program log: stable","Log truncated","Program Vote111 success"],"rewards":[]},"version":0,"transactionIndex":7}"#,
8,
);
assert!(!super::raw_transaction_incoming_truncated_log_messages_compatible(&stored_truncated, &stored));
return;
}
#[test] #[test]
fn v0_3_15_pre_014_log_messages_conflict_diagnostic_detects_strict_prefix_without_exposing_line_material() { fn v0_3_15_pre_014_log_messages_conflict_diagnostic_detects_strict_prefix_without_exposing_line_material() {
let stored = raw_transaction( let stored = raw_transaction(

View File

@@ -0,0 +1,145 @@
<!-- file: deltas/0.3.15/pre.014-fix.001.md -->
<!-- version: 1 -->
# Delta v0.3.15-pre.014-fix.001 — troncature `logMessages` compatible non terminale
## Base requise
```text
ksp-general-0.3.15-pre.014.zip
SHA-256 : 73435098f6b8a7cfc5e3af4c04d2c7ffd3a39f6edb322ef79b21b16757f70b87
bytes : 124399
```
Le gate opérateur de `pre.014` est vert côté audits/check/Clippy/tests. Le live reproduit un unique `content_conflict` Yellowstone pendant l'exécution parallèle avec HTTP Block Polling.
Le diagnostic `pre.014` ferme la cause : au slot `447781296`, les deux routes voient la même identité de bloc et la même transaction. Seul `meta.logMessages` diffère. Le canonique déjà stocké contient `176` lignes sans troncature ; l'entrant contient `118` lignes et le marqueur exact `Log truncated` au premier point divergent. Toutes les lignes précédentes sont identiques.
## Objectif
Ne plus transformer ce cas précis en erreur terminale Worker lorsque la version complète est déjà canonique.
La règle `0.3.15` reste volontairement étroite :
```text
canonique complet
+ incoming avec même identité / slot / block_time / format
+ même transaction / version / transactionIndex
+ mêmes champs meta hors logMessages
+ exactement un marqueur "Log truncated" côté incoming
+ préfixe identique avant le marqueur
=> canonique inchangé
=> RawEntityWriteOutcome::AlreadyPresent
=> observation entrante persistée
=> aucun content_conflict
=> Worker continue
```
Le matériel situé après le marqueur de troncature n'est pas utilisé pour prouver la complétude. Le marqueur constitue la frontière informationnelle ; la preuve porte sur le préfixe antérieur et sur l'égalité de tous les autres champs.
Restent des conflits en `0.3.15` : préfixe différent avant le marqueur, autre champ `meta` différent, transaction/version/index différents, marqueur absent/non exact/multiple, ou sens inverse `canonique tronqué -> incoming plus complet`. Ce dernier cas exige une promotion canonique atomique et est réservé à `0.3.16`.
## Handoff `0.3.16``0.3.18`
La décision opérateur est consignée durablement :
```text
0.3.16 RAW resilience / variantes / conflits / résolutions / retry-reconnect / Store Desk
0.3.17 ksp-job-backfill-lib multi-route / multi-stratégie
0.3.18 ksp-app-backfill-desk adapté au Job multi-route
```
Le contrat détaillé est ajouté dans `docs/plans/037-V0_3_16_RAW_RESILIENCE_CONFLICT_HANDOFF.md`.
`0.3.16` devra notamment introduire les variantes RAW durables, la classification `Exact / CompatibleLessComplete / CompatibleMoreComplete / Conflict`, la promotion canonique réversible, la quarantaine des vrais conflits sans arrêt du Worker, l'historique de résolution, le retry Store sans perte silencieuse, la reconnexion Transport configurable/bornée et les vues de gestion correspondantes dans Store Desk.
## Fichiers ajoutés
```text
docs/plans/037-V0_3_16_RAW_RESILIENCE_CONFLICT_HANDOFF.md
deltas/0.3.15/pre.014-fix.001.md
```
## Fichiers modifiés
```text
Cargo.toml
ROADMAP.md
crates/ksp-store-postgres-lib/src/raw_transaction.rs
crates/ksp-store-postgres-lib/tests/hardening_completeness.rs
crates/ksp-store-postgres-lib/unit_tests/raw_transaction.rs
docs/architecture/004-COMPONENT_INVENTORY.md
docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md
docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md
docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md
docs/plans/031-V0_3_10_RAW_TRANSACTION_INGEST_PLAN.md
docs/plans/036-V0_3_15_RAW_TRANSACTION_INGEST_DESK_PLAN.md
docs/validation/032-V0_3_15_RAW_TRANSACTION_INGEST_DESK.md
```
## Fichiers supprimés
Aucun.
## Version
```text
workspace.package.version : 0.3.15-pre.14.fix.1
root Cargo header counter : 629
```
## Canaris ajoutés
```text
v0_3_15_pre_014_fix_001_incoming_truncated_logs_are_compatible_only_when_missing_middle_is_proven
v0_3_15_pre_014_fix_001_truncated_log_compatibility_rejects_unproven_or_reverse_changes
v0_3_15_pre_014_fix_001_truncated_log_compatibility_is_narrow_and_keeps_canonical_content
```
Les tests vérifient notamment que le préfixe divergent, un autre champ `meta`, un marqueur non exact et le sens inverse restent non compatibles. Le contenu situé après le marqueur n'est volontairement pas traité comme preuve de complétude.
## Validations exécutées dans l'environnement de préparation
```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/0.3.15
Markdown table audit: clean (318 table(s), 224 file(s))
```
Scan statique du diff production : aucun nouvel `unwrap()`, `expect(`, `panic!(`, `unreachable!(`, `unsafe {`, `unbounded_channel` ou `tokio::spawn`.
## Validations non exécutées ici
`cargo` et `rustfmt` ne sont pas disponibles dans l'environnement de préparation. Ne pas considérer les gates suivants comme passés avant exécution opérateur :
```text
cargo fmt --all
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-store-postgres-lib --all-targets --all-features
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features
cargo test -p ksp-app-raw-transaction-ingest-desk --all-targets --all-features
cargo test --workspace --all-targets --all-features
```
## Gate live requis
Lancer Yellowstone + HTTP Block Polling Mainnet en `confirmed` suffisamment longtemps pour recroiser une transaction avec logs tronqués côté provider.
Attendus :
```text
aucun terminal content_conflict pour FULL déjà canonique + TRUNCATED compatible
Yellowstone reste Running
route non Unhealthy/Faulted pour ce cas
canonique complet inchangé
observation de la route tronquée enregistrée
pas de grpc_backpressure_overflow
```
Le cas inverse `TRUNCATED canonique puis FULL entrant` peut encore produire `content_conflict` en `0.3.15`; il est explicitement reporté à la promotion/variant machinery de `0.3.16` et ne doit pas être maquillé par un overwrite destructif dans ce fix.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/004-COMPONENT_INVENTORY.md --> <!-- file: docs/architecture/004-COMPONENT_INVENTORY.md -->
<!-- version: 41 --> <!-- version: 42 -->
# Inventaire initial des composants KSP # Inventaire initial des composants KSP
@@ -18,46 +18,46 @@ Ce document maintient l'inventaire synthétique des composants retenus ou presse
## Inventaire synthétique ## Inventaire synthétique
| Domaine | Composant | Type | Statut | Première cible actuelle | Mission | | Domaine | Composant | Type | Statut | Première cible actuelle | Mission |
|-------------------------|-----------------------------------------|-------------|--------------|---------------------------------|---------------------------------------------------------------------------------------------| |-------------------------|-----------------------------------------|-------------|--------------|---------------------------------|--------------------------------------------------------------------------------------------------|
| Core | `ksp-core-lib` | lib | Stable | `0.1.1` | Error/Result, Program IDs et primitives fondamentales | | Core | `ksp-core-lib` | lib | Stable | `0.1.1` | Error/Result, Program IDs et primitives fondamentales |
| Logging | `ksp-logging-lib` | lib | Stable | `0.1.2` | façade unique tracing KSP | | Logging | `ksp-logging-lib` | lib | Stable | `0.1.2` | façade unique tracing KSP |
| Config | `ksp-config-lib` | lib | Stable | `0.1.3` | documents, profils, env et persistence Config | | Config | `ksp-config-lib` | lib | Stable | `0.1.3` | documents, profils, env et persistence Config |
| Config Desk | `ksp-app-config-desk` | app | Stable | `0.1.4` | validation/management Config | | Config Desk | `ksp-app-config-desk` | app | Stable | `0.1.4` | validation/management Config |
| On-chain HTTP | `ksp-onchain-transport-lib` | lib | Stable | `0.2.1``0.2.4` | HTTP standard complet : 52/52 current + 14/14 historical | | On-chain HTTP | `ksp-onchain-transport-lib` | lib | Stable | `0.2.1``0.2.4` | HTTP standard complet : 52/52 current + 14/14 historical |
| Wallet | `ksp-wallet-lib` | lib | Stable | `0.2.5` | `.kspwallet`, VIEW/OWNER, secrets, signature, import/export | | Wallet | `ksp-wallet-lib` | lib | Stable | `0.2.5` | `.kspwallet`, VIEW/OWNER, secrets, signature, import/export |
| Wallet Desk | `ksp-app-wallet-desk` | app | Stable | `0.2.6` | Wallet + Config + balance HTTP + projection SOL/USD auxiliaire | | Wallet Desk | `ksp-app-wallet-desk` | app | Stable | `0.2.6` | Wallet + Config + balance HTTP + projection SOL/USD auxiliaire |
| Wallet V2 | `ksp-wallet-lib` | lib | Stable | `0.2.6` | wire/runtime V2 + API default/versionnée + migration explicite | | Wallet V2 | `ksp-wallet-lib` | lib | Stable | `0.2.6` | wire/runtime V2 + API default/versionnée + migration explicite |
| Standard WS | `ksp-onchain-transport-lib` | lib | Stable | `0.2.7` | WebSocket Solana 18/18, sessions/subscriptions bornées | | Standard WS | `ksp-onchain-transport-lib` | lib | Stable | `0.2.7` | WebSocket Solana 18/18, sessions/subscriptions bornées |
| Helius WS | `ksp-onchain-transport-lib` | lib | Stable | `0.2.8` | LaserStream WS : 7 standard + transaction, actor partagé | | Helius WS | `ksp-onchain-transport-lib` | lib | Stable | `0.2.8` | LaserStream WS : 7 standard + transaction, actor partagé |
| Yellowstone | `ksp-onchain-transport-lib` | lib | Stable | `0.2.9` | client gRPC standard/provider-neutral | | Yellowstone | `ksp-onchain-transport-lib` | lib | Stable | `0.2.9` | client gRPC standard/provider-neutral |
| Off-chain price | `ksp-offchain-transport-lib` | lib | Stable | `0.2.11` | prix SOL/USD multi-provider, limits et availability | | Off-chain price | `ksp-offchain-transport-lib` | lib | Stable | `0.2.11` | prix SOL/USD multi-provider, limits et availability |
| SOL Prices Desk | `ksp-app-solprices-desk` | app | Stable | `0.2.12` | HID provider-agnostic pour visualisation/refresh prix | | SOL Prices Desk | `ksp-app-solprices-desk` | app | Stable | `0.2.12` | HID provider-agnostic pour visualisation/refresh prix |
| Interface passive | `ksp-interface-lib` | lib | Stable | `0.2.13` | façade wire + contrats passifs partagés, dont événements acquisition provider-neutral | | Interface passive | `ksp-interface-lib` | lib | Stable | `0.2.13` | façade wire + contrats passifs partagés, dont événements acquisition provider-neutral |
| Program API | `ksp-program-api` | API | Stable | `0.2.14` | contrats extensibles Program | | Program API | `ksp-program-api` | API | Stable | `0.2.14` | contrats extensibles Program |
| Program impl. | `ksp-program-lib` | lib | Retenu | vertical slices ultérieurs | implementations Program officielles | | Program impl. | `ksp-program-lib` | lib | Retenu | vertical slices ultérieurs | implementations Program officielles |
| Program extension | `ksp-program-<name>-lib` | lib externe | À la demande | dès besoin | implementation externe de `ksp-program-api` | | Program extension | `ksp-program-<name>-lib` | lib externe | À la demande | dès besoin | implementation externe de `ksp-program-api` |
| Store API | `ksp-store-api` | API | Stable | `0.3.1` | RAW transaction/account, observations, queries, outcomes, rétention et capabilities backend | | Store API | `ksp-store-api` | API | Stable | `0.3.1`, extension `0.3.16` | RAW transaction/account, observations ; variantes/conflits/résolutions ajoutés sans backend leak |
| Store runtime | `ksp-store-lib` | lib | Stable | `0.3.2``0.3.4` | façade backend-neutral et conformance RAW 10/10 | | Store runtime | `ksp-store-lib` | lib | Stable | `0.3.2``0.3.4`, `0.3.16` | façade backend-neutral ; convergence, retry et résolution RAW partagés entre producteurs |
| Store PostgreSQL | `ksp-store-postgres-lib` | lib | Stable | `0.3.2``0.3.4` | backend référence : fondation, RawTransaction et RawAccountState | | Store PostgreSQL | `ksp-store-postgres-lib` | lib | Stable | `0.3.2``0.3.4`, `0.3.16` | backend référence ; variantes conflictuelles et historique de résolution atomiques en `0.3.16` |
| Job lifecycle | `ksp-job-api` | API | Implémenté | `0.3.6` | identité/lifecycle/annulation/notifications latest-value runtime-neutral | | Job lifecycle | `ksp-job-api` | API | Implémenté | `0.3.6` | identité/lifecycle/annulation/notifications latest-value runtime-neutral |
| Backfill | `ksp-job-backfill-lib` | lib | Implémenté | `0.3.6`, extension `0.3.16` | première verticale HTTP puis extension multi-source/historique sans changer lidentité RAW | | Backfill | `ksp-job-backfill-lib` | lib | Implémenté | `0.3.6`, extension `0.3.17` | première verticale HTTP puis extension multi-route/historique sur convergence Store partagée |
| Backfill Desk | `ksp-app-backfill-desk` | app | Implémenté | `0.3.7`, extension `0.3.16` | contrôle du backfill RAW ; sélection des stratégies/sources ajoutée après le worker live | | Backfill Desk | `ksp-app-backfill-desk` | app | Implémenté | `0.3.7`, extension `0.3.18` | contrôle du backfill RAW ; sélection/supervision multi-route adaptée au Job `0.3.17` |
| Store Desk | `ksp-app-store-desk` | app | Implémenté | `0.3.8` | inspection RAW read-only Transaction/Account/Observation via façade Store | | Store Desk | `ksp-app-store-desk` | app | Implémenté | `0.3.8`, extension `0.3.16` | inspection RAW + vues conflits/variantes/historique et résolution manuelle réversible |
| RAW transaction common | `ksp-raw-transaction-lib` | lib | Implémenté | `0.3.10` | canonicalisation/wire RAW Transaction v1 source-neutral partagé entre producteurs | | RAW transaction common | `ksp-raw-transaction-lib` | lib | Implémenté | `0.3.10` | canonicalisation/wire RAW Transaction v1 source-neutral partagé entre producteurs |
| Worker lifecycle | `ksp-worker-api` | API | Implémenté | `0.3.9` | lifecycle/health/progression génériques des services continus | | Worker lifecycle | `ksp-worker-api` | API | Implémenté | `0.3.9` | lifecycle/health/progression génériques des services continus |
| RAW transaction worker | `ksp-worker-raw-transaction-ingest-lib` | worker/lib | Implémenté | `0.3.11``0.3.14` | 5 familles live + gaps/coverage/repair run-local, fairness, health et shutdown bornés | | RAW transaction worker | `ksp-worker-raw-transaction-ingest-lib` | worker/lib | Implémenté | `0.3.11``0.3.16` | live multi-route ; `0.3.16` sépare défaut transport et conflits RAW locaux durablement gérés |
| RAW ingest Desk | `ksp-app-raw-transaction-ingest-desk` | app | Retenu | `0.3.15` | choix/supervision dune ou plusieurs sources/méthodes sans réimplémenter le worker | | RAW ingest Desk | `ksp-app-raw-transaction-ingest-desk` | app | Retenu | `0.3.15` | choix/supervision dune ou plusieurs sources/méthodes sans réimplémenter le worker |
| STRUCTURAL job | nom à fixer | job/lib | Retenu | couche STRUCTURAL | normalisation Solana générique RAW -> STRUCTURAL bornée/rejouable | | STRUCTURAL job | nom à fixer | job/lib | Retenu | couche STRUCTURAL | normalisation Solana générique RAW -> STRUCTURAL bornée/rejouable |
| STRUCTURAL worker | nom à fixer | worker/lib | Retenu | fin couche STRUCTURAL | backlog RAW -> STRUCTURAL continu | | STRUCTURAL worker | nom à fixer | worker/lib | Retenu | fin couche STRUCTURAL | backlog RAW -> STRUCTURAL continu |
| Materializer API | `ksp-materializer-api` | API | Retenu | premier groupe DECODED | contrats extensibles matérialisation | | Materializer API | `ksp-materializer-api` | API | Retenu | premier groupe DECODED | contrats extensibles matérialisation |
| Materializer impl. | `ksp-materializer-lib` | lib | Retenu | premier groupe DECODED | implementations officielles communes | | Materializer impl. | `ksp-materializer-lib` | lib | Retenu | premier groupe DECODED | implementations officielles communes |
| Execution policy | `ksp-execution-policy-api` | API | Retenu | premier vrai besoin execution | décision/safety multi-contexte | | Execution policy | `ksp-execution-policy-api` | API | Retenu | premier vrai besoin execution | décision/safety multi-contexte |
| Execution orchestration | `ksp-execution-lib` | lib | Retenu | premier vrai cycle execution | Program + policy + Wallet + transport | | Execution orchestration | `ksp-execution-lib` | lib | Retenu | premier vrai cycle execution | Program + policy + Wallet + transport |
| Scenarios | `ksp-scenario-<domain>-lib` | lib | Retenu | vertical slices | validation métier/devnet par groupe | | Scenarios | `ksp-scenario-<domain>-lib` | lib | Retenu | vertical slices | validation métier/devnet par groupe |
| Scenario API | `ksp-scenario-api` | API | Non retenu | — | norme souple avant trait commun | | Scenario API | `ksp-scenario-api` | API | Non retenu | — | norme souple avant trait commun |
| Market Desk | `ksp-app-market-desk` | app | Pressenti | après Meteora/Raydium/Pump/Orca | tokens, pools, trades, liquidity, price, OHLC | | Market Desk | `ksp-app-market-desk` | app | Pressenti | après Meteora/Raydium/Pump/Orca | tokens, pools, trades, liquidity, price, OHLC |
| Trading Intelligence | noms à définir | libs/jobs | Pressenti | après données stables | features/signaux/anomalies/ML | | Trading Intelligence | noms à définir | libs/jobs | Pressenti | après données stables | features/signaux/anomalies/ML |
## Contrats séparés retenus ## Contrats séparés retenus

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md --> <!-- file: docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md -->
<!-- version: 20 --> <!-- version: 21 -->
# Acquisition, workers, jobs et pipelines spécialisés # Acquisition, workers, jobs et pipelines spécialisés
@@ -90,7 +90,7 @@ Le job :
Le caller desktop spécialisé actuel est `ksp-app-backfill-desk`. Il compose Config, le pool HTTP et Store, puis remet ces ressources au runtime Backfill. Il peut retenir le checkpoint terminal uniquement en mémoire Rust pour un Resume in-session ; cette rétention applicative ne transforme pas le checkpoint en garantie de reprise durable après redémarrage. Le caller desktop spécialisé actuel est `ksp-app-backfill-desk`. Il compose Config, le pool HTTP et Store, puis remet ces ressources au runtime Backfill. Il peut retenir le checkpoint terminal uniquement en mémoire Rust pour un Resume in-session ; cette rétention applicative ne transforme pas le checkpoint en garantie de reprise durable après redémarrage.
Cette verticale `0.3.6`/`0.3.7` est **la première stratégie de backfill**, pas la définition générale du backfill KSP. Son discovery `getSignaturesForAddress` + hydration `getTransaction` est HTTP parce que cette méthode a été choisie pour le premier vertical slice. Après stabilisation du worker live et de sa Desk, `0.3.16` doit réauditer `ksp-job-backfill-lib` et `ksp-app-backfill-desk` pour intégrer les autres stratégies historiques/catch-up pertinentes identifiées par l'audit RAW Transaction de `0.3.9`. Cette verticale `0.3.6`/`0.3.7` est **la première stratégie de backfill**, pas la définition générale du backfill KSP. Son discovery `getSignaturesForAddress` + hydration `getTransaction` est HTTP parce que cette méthode a été choisie pour le premier vertical slice. Après stabilisation du worker live et de sa Desk, `0.3.16` ferme d'abord la résilience RAW partagée : divergences compatibles, variantes conflictuelles durables, résolution/restauration canonique, retry Store et reconnexion Transport. `0.3.17` réaudite ensuite `ksp-job-backfill-lib` pour intégrer les autres stratégies historiques/catch-up pertinentes identifiées par l'audit RAW Transaction de `0.3.9`, puis `0.3.18` adapte `ksp-app-backfill-desk` au Job multi-route.
### Worker RAW Transaction live ### Worker RAW Transaction live

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md --> <!-- file: docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md -->
<!-- version: 16 --> <!-- version: 17 -->
# Acquisition et alimentation `RawTransaction` # Acquisition et alimentation `RawTransaction`
@@ -869,7 +869,30 @@ settings techniques nécessaires
Elle peut activer plusieurs providers/endpoints simultanément. Les secrets restent Config-owned, avec réutilisation unique de `KSP_SECRET_HELIUS_API_KEY` pour les surfaces Helius concernées. Elle peut activer plusieurs providers/endpoints simultanément. Les secrets restent Config-owned, avec réutilisation unique de `KSP_SECRET_HELIUS_API_KEY` pour les surfaces Helius concernées.
## 15. Handoff `0.3.16` : Job Backfill multi-stratégie ## 14.5 Handoff `0.3.16` : résilience RAW et conflits durables
`0.3.16` doit déplacer la résolution des divergences RAW dans le contrat Store partagé, afin que Worker live et futurs Jobs historiques convergent avec la même politique. Une divergence de contenu n'est plus assimilée automatiquement à une panne d'acquisition.
Modèle cible :
```text
RawTransaction identity (network, signature)
-> canonical_variant
-> 1..N variantes de contenu durables
-> observations/provenances rattachées à la variante réellement observée
-> conflict case éventuel
-> historique immutable des promotions/résolutions
```
Classification minimale : `Exact`, `CompatibleLessComplete`, `CompatibleMoreComplete`, `Conflict`. Une représentation prouvée moins complète ne remplace jamais le canonique ; une représentation prouvée plus complète peut le promouvoir atomiquement ; un vrai conflit conserve toutes les variantes sans arrêter l'acquisition. La provenance d'un provider n'établit jamais à elle seule une priorité canonique.
Le Worker doit distinguer les problèmes locaux durablement gérables des pannes externes. Un conflit RAW quarantiné laisse la route active et rend la santé au plus `Degraded`. Une indisponibilité Store impose backpressure/pause et retry sans perte silencieuse. Une indisponibilité Transport déclenche une politique de reconnexion bornée/configurable (`initial_delay`, `max_delay`, backoff, `max_attempts`, reset après stabilité). Un terminal `Faulted` reste réservé aux erreurs irréconciliables ou à l'épuisement explicite d'une politique de retry/reconnect.
`ksp-app-store-desk` doit ajouter des vues de conflits/variantes et d'historique. Une résolution manuelle peut promouvoir une variante, conserver le canonique, ou produire une variante de fusion assistée. Toute ancienne valeur canonique reste une variante durable et peut être restaurée ultérieurement ; aucun `UPDATE` destructif ne doit rendre une décision manuelle irréversible.
Le détail de handoff est fixé dans `docs/plans/037-V0_3_16_RAW_RESILIENCE_CONFLICT_HANDOFF.md`.
## 15. Handoff `0.3.17` : Job Backfill multi-stratégie
Le Job doit conserver son vertical slice existant puis ajouter des stratégies historiques choisies selon requête + capabilities + configuration. Le Job doit conserver son vertical slice existant puis ajouter des stratégies historiques choisies selon requête + capabilities + configuration.
@@ -901,10 +924,10 @@ Ordre conseillé :
0.3.13 P1 : multi-source convergence, dedup, provenance, content conflict, backpressure/fairness 0.3.13 P1 : multi-source convergence, dedup, provenance, content conflict, backpressure/fairness
0.3.13 P2 : health source-neutral, shutdown/races, completeness/security et gate live keyless 0.3.13 P2 : health source-neutral, shutdown/races, completeness/security et gate live keyless
0.3.14 : trajectoire suivante définie par son prompt dédié ; ne pas réintroduire implicitement un scope reporté 0.3.14 : trajectoire suivante définie par son prompt dédié ; ne pas réintroduire implicitement un scope reporté
0.3.16 P0 : block scan historique 0.3.17 P0 : block scan historique
0.3.16 P0 : replay Yellowstone borné 0.3.17 P0 : replay Yellowstone borné
0.3.16 P1 : provider history/archive 0.3.17 P1 : provider history/archive
0.3.16 P1 : Old Faithful 0.3.17 P1 : Old Faithful
plus tard : substrats directs et sources vendor-specific sans accès actuel plus tard : substrats directs et sources vendor-specific sans accès actuel
``` ```
@@ -1026,7 +1049,9 @@ TR-C2 = adapter productif Transport DTO -> common réserv
0.3.11 = fondation Worker source-neutral, persistence et observabilité 0.3.11 = fondation Worker source-neutral, persistence et observabilité
0.3.12 = première verticale Yellowstone + hydration/replay continuity 0.3.12 = première verticale Yellowstone + hydration/replay continuity
0.3.13 = cinq familles live + convergence/fairness/health/shutdown/completeness 0.3.13 = cinq familles live + convergence/fairness/health/shutdown/completeness
0.3.16 = Job Backfill multi-stratégie historique 0.3.16 = résilience RAW, variantes/conflits/résolutions, retry/reconnect et Store Desk
0.3.17 = Job Backfill multi-route/multi-stratégie historique
0.3.18 = Backfill Desk multi-route
``` ```
`0.3.9` n'implémente aucune nouvelle stratégie d'acquisition. Il ferme l'architecture et l'inventaire nécessaires aux releases suivantes. `0.3.9` n'implémente aucune nouvelle stratégie d'acquisition. Il ferme l'architecture et l'inventaire nécessaires aux releases suivantes.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md --> <!-- file: docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md -->
<!-- version: 100 --> <!-- version: 101 -->
# Séquence des releases fonctionnelles KSP # Séquence des releases fonctionnelles KSP
@@ -587,7 +587,9 @@ La séquence effective a évolué par sizing et validation réels. La référenc
0.3.13 cinq familles live + convergence multi-source/fairness/health/hardening 0.3.13 cinq familles live + convergence multi-source/fairness/health/hardening
0.3.14 tranche suivante de la trajectoire RAW selon prompt dédié 0.3.14 tranche suivante de la trajectoire RAW selon prompt dédié
0.3.15 ksp-app-raw-transaction-ingest-desk 0.3.15 ksp-app-raw-transaction-ingest-desk
0.3.16 Backfill multi-source / multi-stratégie 0.3.16 RAW resilience / conflict variants / Store Desk conflict management
0.3.17 ksp-job-backfill-lib multi-route / multi-stratégie
0.3.18 ksp-app-backfill-desk adapté au Backfill multi-route
``` ```
Cette série ferme la couche RAW et ses outils d'exploitation avant l'ouverture fonctionnelle de STRUCTURAL. Le Worker live RAW reste distinct des jobs historiques et ne constitue pas encore une transformation RAW -> STRUCTURAL. Cette série ferme la couche RAW et ses outils d'exploitation avant l'ouverture fonctionnelle de STRUCTURAL. Le Worker live RAW reste distinct des jobs historiques et ne constitue pas encore une transformation RAW -> STRUCTURAL.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/031-V0_3_10_RAW_TRANSACTION_INGEST_PLAN.md --> <!-- file: docs/plans/031-V0_3_10_RAW_TRANSACTION_INGEST_PLAN.md -->
<!-- version: 14 --> <!-- version: 15 -->
# Plan v0.3.10 — RAW Transaction commune + qualification cross-source # Plan v0.3.10 — RAW Transaction commune + qualification cross-source
@@ -1084,9 +1084,17 @@ Nouvelle release/session : replay natif, source redondante, scan HTTP blocs, hyd
Ancienne cible `0.3.11`, décalée après finalisation complète du Worker. Ancienne cible `0.3.11`, décalée après finalisation complète du Worker.
### `0.3.16` — Backfill multi-source/multi-stratégie ### `0.3.16` — RAW resilience / conflict management
Ancienne cible `0.3.12`, décalée sans changer la frontière Job historique paramétré / Worker live. Nouvelle tranche intercalée après le live `0.3.15` : variantes RAW durables, résolution automatique des représentations partielles prouvées, quarantaine des vrais conflits, historique de promotion/résolution, retry Store, reconnexion Transport configurable et vues de gestion dans Store Desk.
### `0.3.17` — Backfill multi-route/multi-stratégie
Ancienne cible Backfill multi-source, décalée sans changer la frontière Job historique paramétré / Worker live.
### `0.3.18` — Backfill Desk multi-route
Adapter `ksp-app-backfill-desk` au Job `0.3.17` en réutilisant les patterns de composition/supervision du Raw Transaction Ingest Desk sans dupliquer la logique Worker.
### Note durable de génération des prompts `0.3.12` à `0.3.15` ### Note durable de génération des prompts `0.3.12` à `0.3.15`
@@ -1169,7 +1177,7 @@ Les smokes live restent séparés et opt-in.
```text ```text
ksp-app-raw-transaction-ingest-desk # 0.3.15 ksp-app-raw-transaction-ingest-desk # 0.3.15
nouvelles stratégies historiques Backfill # 0.3.16 nouvelles stratégies historiques Backfill # 0.3.17
paramètres métier historiques dans Worker paramètres métier historiques dans Worker
Backfill checkpoint dans Worker Backfill checkpoint dans Worker
RawAccountState ingest worker RawAccountState ingest worker

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/036-V0_3_15_RAW_TRANSACTION_INGEST_DESK_PLAN.md --> <!-- file: docs/plans/036-V0_3_15_RAW_TRANSACTION_INGEST_DESK_PLAN.md -->
<!-- version: 22 --> <!-- version: 23 -->
# Plan v0.3.15 — Raw Transaction Ingest Desk # Plan v0.3.15 — Raw Transaction Ingest Desk
@@ -956,3 +956,16 @@ La cause est la même pour les deux routes et se situe dans le drain Worker comm
Le fault Yellowstone Devnet observé dans le même gate reste distinct : OrbitFlare répond `onchain_transport.grpc_status` avec refus de permission dès louverture du Subscribe, avant toute demande de Stop. Il nest pas reclassé par ce correctif. Le fault Yellowstone Devnet observé dans le même gate reste distinct : OrbitFlare répond `onchain_transport.grpc_status` avec refus de permission dès louverture du Subscribe, avant toute demande de Stop. Il nest pas reclassé par ce correctif.
## 23. Handoff durable après `pre.014-fix.001`
Le diagnostic inter-provider de `pre.014` et sa correction minimale `fix.001` ouvrent une évolution Store/Worker qui ne doit pas être absorbée par `0.3.15`. Le cas démontré de `logMessages` tronqués est fermé localement, mais la politique générale de divergence durable est réservée à `0.3.16`.
Trajectoire retenue :
```text
0.3.16 RAW resilience + conflict variants/resolution + Store Desk conflicts
0.3.17 ksp-job-backfill-lib multi-route / multi-stratégie
0.3.18 ksp-app-backfill-desk adapté au Job multi-route
```
Le contrat complet de handoff est enregistré dans `docs/plans/037-V0_3_16_RAW_RESILIENCE_CONFLICT_HANDOFF.md`. La préparation de publication `0.3.15` doit générer le prompt `0.3.16` depuis ce handoff et non depuis l'ancienne cible Backfill historique.

View File

@@ -0,0 +1,178 @@
<!-- file: docs/plans/037-V0_3_16_RAW_RESILIENCE_CONFLICT_HANDOFF.md -->
<!-- version: 1 -->
# Handoff v0.3.16 — RAW resilience, variantes et gestion des conflits
## 1. Origine de la version
Le live `0.3.15-pre.014` a démontré qu'une même transaction du même bloc peut être renvoyée avec une qualité de représentation différente selon le nœud RPC. Le cas observé n'était pas un fork : même slot, même identité de bloc, même transaction et mêmes métadonnées hors `logMessages`. Une acquisition contenait les logs complets ; l'autre contenait un marqueur `Log truncated`.
`0.3.15-pre.014-fix.001` ferme uniquement le cas où la version complète est déjà canonique et où une version tronquée strictement compatible arrive ensuite. La généralisation ci-dessous appartient à `0.3.16`.
## 2. Principe directeur
```text
une divergence de données != une panne d'acquisition
```
Le pipeline ne doit plus arrêter un Worker simplement parce qu'un N-ième RAW d'identité `(network, signature)` diffère du canonique. Store doit d'abord classifier la divergence, préserver les données et seulement exposer un conflit durable lorsqu'aucune convergence automatique sûre n'est démontrée.
Un provider n'est jamais déclaré « toujours correct ». La préférence porte sur la qualité prouvée de la représentation, indépendamment de l'ordre d'arrivée et de la provenance.
## 3. Classification de convergence
Le comparateur partagé vise au minimum :
```text
Exact
CompatibleLessComplete
CompatibleMoreComplete
Conflict
```
Règles :
- `Exact` : contenu canonique identique ; observation idempotente/nouvelle ;
- `CompatibleLessComplete` : conserver le canonique plus complet et l'observation entrante ;
- `CompatibleMoreComplete` : promotion atomique vers la représentation plus complète, sans détruire l'ancienne variante ;
- `Conflict` : conserver le canonique courant, conserver intégralement la variante entrante et ouvrir/compléter un dossier de conflit.
La comparaison est champ-spécifique. Il est interdit d'utiliser une règle générique « valeur la plus longue gagne ». `logMessages` peut utiliser une relation de troncature explicitement prouvée ; les autres champs ne deviennent enrichissables que lorsque leur contrat permet de distinguer absence informationnelle et contradiction.
## 4. Modèle Store cible
Le modèle conceptuel devient :
```text
RawTransaction identity (network, signature)
|
+-- Variant A <- canonical current
| +-- observations/provenances
|
+-- Variant B
| +-- observations/provenances
|
+-- Variant C
+-- observations/provenances
Conflict case
+-- status
+-- canonical variant
+-- classification
+-- resolution history
```
Les noms physiques exacts restent à décider en `0.3.16-pre.001`, mais les responsabilités doivent couvrir : identité canonique, variantes de contenu, observations rattachées à la variante réellement reçue, dossier de conflit et historique de résolution/promotion.
Une résolution ou promotion n'efface jamais l'ancienne valeur canonique. Un `A -> B` doit pouvoir être suivi plus tard d'un `B -> A` sans reconstruire les bytes depuis une source externe.
## 5. Résolution automatique
Le cas `logMessages` sert de première preuve :
```text
FULL + TRUNCATED compatible
-> garder FULL
TRUNCATED + FULL compatible
-> promouvoir FULL
TRUNCATED A + TRUNCATED B compatibles
-> promouvoir uniquement si la relation de qualité est formellement démontrée
```
Les champs scalaires présents avec des valeurs différentes restent des conflits. Les listes ne sont jamais fusionnées par longueur seule. Une absence peut devenir enrichissable uniquement si le contrat RPC/source prouve que l'absence signifie « non fourni » et non une valeur métier différente.
## 6. Conflit durable et santé Worker
Un conflit durablement quarantiné n'est pas terminal :
```text
content conflict
-> variant persisted
-> conflict case unresolved
-> Worker Running
-> health Degraded
```
Les snapshots doivent pouvoir exposer au minimum des compteurs source-neutral et sans identité métier, par exemple `unresolved_content_conflict_count`, `auto_resolved_conflict_count` et `canonical_promotion_count`. Les signatures, payloads et variantes restent Store-owned.
La complétude doit distinguer :
```text
acquisition_complete
= toute donnée attendue est durablement capturée, variantes comprises
canonical_complete
= aucune obligation de réconciliation canonique n'est ouverte
```
Un conflit non résolu ne doit pas bloquer l'acquisition des autres transactions. Le passage RAW -> STRUCTURAL décidera séparément comment traiter une identité encore conflictuelle.
## 7. Store indisponible, retry et backpressure
Un Store temporairement indisponible est différent d'un content conflict. Le Worker ne doit ni perdre silencieusement les données ni continuer à consommer indéfiniment sans durabilité.
Le contrat `0.3.16` doit prévoir une politique Store bornée : pause/backpressure des admissions, retry, délais/backoff configurables dans la couche propriétaire appropriée, observabilité `Degraded/Blocked`, puis terminal seulement si la politique explicite est épuisée ou si l'erreur est structurellement irréconciliable.
## 8. Reconnexion Transport
La reconnexion Transport doit être explicitement configurable et distincte de la politique Store. Paramètres conceptuels à auditer en `pre.001` :
```text
initial_delay
max_delay
backoff_multiplier
max_attempts
reset_after_stable_duration
jitter borné si retenu
```
Une perte de connexion ne doit pas provoquer un arrêt immédiat tant que la politique de reconnexion n'est pas épuisée. Les gaps éventuels continuent à utiliser les mécanismes de continuité/repair du Worker ; aucune reconnexion ne vaut preuve de couverture.
## 9. `ksp-app-store-desk`
`0.3.16` étend Store Desk avec une ou plusieurs vues dédiées, au minimum autour de deux responsabilités : conflits ouverts et historique de résolution.
Pour une identité, l'UI doit pouvoir présenter le canonique, les variantes, leurs différences structurées et leurs provenances sans charger arbitrairement tout le RAW dans les tables. Actions visées : promouvoir une variante, conserver le canonique et résoudre, fusion assistée uniquement pour des champs dont la compatibilité est définie, rouvrir/restaurer une résolution antérieure.
Une fusion manuelle produit une nouvelle variante explicitement marquée comme synthétique/manuelle avec ses parents ; elle ne prétend jamais être une observation provider native. Aucune action utilisateur ne doit rendre les anciennes variantes irrécupérables.
## 10. Frontières de crates
```text
ksp-store-api
-> DTO/contrats backend-neutral variantes, conflits, résolutions
ksp-store-lib
-> façade unique et moteur de convergence exposé aux producteurs
ksp-store-postgres-lib
-> atomicité physique, schema/migrations, variantes et historique
ksp-worker-raw-transaction-ingest-lib
-> continue sur conflits locaux durables, retry/reconnect selon contrats existants
ksp-app-store-desk
-> inspection/résolution via ksp-store-lib uniquement
```
Aucun Worker ou Job ne dépend directement du backend PostgreSQL. Le moteur de convergence est partagé afin que les futurs producteurs n'implémentent pas chacun leur propre arbitrage.
## 11. Versions suivantes
La trajectoire retenue est désormais :
```text
0.3.16 RAW resilience / conflict variants / Store Desk conflict management
0.3.17 ksp-job-backfill-lib multi-route / multi-stratégie
0.3.18 ksp-app-backfill-desk adapté au Job multi-route
```
`0.3.17` conserve le Job borné et paramétré, mais lui permet de composer plusieurs routes/stratégies d'acquisition sur la même convergence Store `0.3.16`. Il ne dépend pas du Worker.
`0.3.18` adapte le Desk Backfill à ce Job : inventaire/composition de routes, sélection, supervision, progression et résultats, en réutilisant les patterns applicatifs prouvés par `ksp-app-raw-transaction-ingest-desk` sans dupliquer la logique métier du Job.
## 12. Hors périmètre de `0.3.15-pre.014-fix.001`
Le fix courant ne crée aucune table de variante, ne promeut pas un canonique tronqué vers une version plus complète, ne modifie pas le health model Worker, n'ajoute pas de retry Store et ne change pas la politique de reconnexion Transport. Il ferme seulement le cas live démontré où une observation entrante tronquée et strictement compatible arrive après un canonique complet.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/validation/032-V0_3_15_RAW_TRANSACTION_INGEST_DESK.md --> <!-- file: docs/validation/032-V0_3_15_RAW_TRANSACTION_INGEST_DESK.md -->
<!-- version: 28 --> <!-- version: 29 -->
# Validation v0.3.15 — Raw Transaction Ingest Desk # Validation v0.3.15 — Raw Transaction Ingest Desk
@@ -960,3 +960,29 @@ incoming_prefix_of_stored=true ou stored_prefix_of_incoming=true
``` ```
Non-claims : `pre.014` ne retire pas `logMessages` du contenu canonique, ne choisit aucun provider comme vérité, ne transforme pas `content_conflict` en succès, ne modifie pas le schema Store et n'expose aucun nouveau type public Worker/Store. Non-claims : `pre.014` ne retire pas `logMessages` du contenu canonique, ne choisit aucun provider comme vérité, ne transforme pas `content_conflict` en succès, ne modifie pas le schema Store et n'expose aucun nouveau type public Worker/Store.
### `pre.014-fix.001` — observation `logMessages` tronquée compatible non terminale
Le live `pre.014` ferme le diagnostic du conflit Mainnet observé au slot `447781296`. HTTP Block Polling et Yellowstone Block Hydration ont produit la même identité de bloc (`parent_slot=447781295`, `block_height=425822496`, même fingerprint), la même transaction, le même index et les mêmes autres métadonnées. La seule divergence est `meta.logMessages`.
Le canonique déjà stocké contient `176` lignes sans marqueur de troncature. L'acquisition entrante contient `118` lignes et un marqueur exact `Log truncated` au premier point divergent. Le diagnostic prouve que toutes les lignes antérieures à ce marqueur sont identiques. Le défaut n'est donc ni un fork ni un changement de transaction : c'est une représentation RPC explicitement tronquée du même résultat d'exécution.
`fix.001` ajoute une compatibilité volontairement asymétrique et étroite dans le backend PostgreSQL :
```text
canonique déjà complet
+ entrant explicitement tronqué et moins complet au-delà du marqueur
+ mêmes network/signature/slot/block_time/format
+ mêmes transaction/version/transactionIndex
+ mêmes champs meta hors logMessages
+ un seul marqueur exact "Log truncated" côté entrant
+ préfixe avant marqueur identique
+ préfixe exact jusquau marqueur ; le matériel après le marqueur nest pas utilisé pour prouver la complétude
=> AlreadyPresent + observation persistée
=> aucun content_conflict
=> Worker continue
```
Le canonical RAW n'est jamais remplacé par la version tronquée. Les cas suivants restent des `content_conflict` en `0.3.15` : préfixe différent avant le marqueur, autre champ `meta` différent, marqueur non exact ou multiple, canonique déjà tronqué puis version plus complète entrante. Ce dernier cas nécessite une promotion canonique atomique et appartient explicitement à `0.3.16`, avec variantes durables et historique de résolution ; il n'est pas simulé par un overwrite opportuniste dans ce fix.
Le correctif ne change aucune API publique Store/Worker, aucun schema SQL et aucun outcome public. `RawEntityWriteOutcome::AlreadyPresent` reste l'outcome canonique, l'observation entrante est conservée normalement et le Worker ne reçoit plus d'erreur pour le cas compatible démontré.