28 Commits

Author SHA1 Message Date
002d08ba5f v0.3.14-rel.001 2026-09-12 23:46:51 +02:00
a7ac30f39b v0.3.14-pre.016 2026-09-12 23:31:19 +02:00
cd7cf2c4c6 v0.3.14-pre.015 2026-09-12 23:07:58 +02:00
8d8b6b9bcb v0.3.14-pre.014 2026-09-12 21:29:00 +02:00
02012f105a v0.3.14-pre.013-fix.001 2026-09-12 20:55:57 +02:00
09c91172ae v0.3.14-pre.013 2026-09-12 20:43:43 +02:00
8a0717c839 v0.3.14-pre.012 2026-09-12 20:28:00 +02:00
f3c4169752 v0.3.14-pre.011-fix.002 2026-09-12 19:29:26 +02:00
eedad4b81e v0.3.14-pre.011-fix.001 2026-09-12 19:07:27 +02:00
4e39ddd5d0 v0.3.14-pre.011 2026-09-12 19:05:43 +02:00
c99cb048bf v0.3.14-pre.010-fix.001 2026-09-12 18:11:16 +02:00
8c4b458724 v0.3.14-pre.010 2026-09-12 14:20:02 +02:00
792560191c v0.3.14-pre.009-fix.002 2026-09-12 13:04:49 +02:00
c884ce88ba v0.3.14-pre.009-fix.001 2026-09-12 12:30:27 +02:00
4bcd942928 v0.3.14-pre.009 2026-09-12 11:22:53 +02:00
bf394e0e4b v0.3.14-pre.008-fix.002 2026-09-12 09:45:58 +02:00
ec94001ab8 v0.3.14-pre.008-fix.001 2026-09-12 09:44:36 +02:00
505cc8c3f6 v0.3.14-pre.008 2026-09-12 09:00:36 +02:00
9fc30d3ab9 v0.3.14-pre.007-fix.001 2026-09-12 08:04:19 +02:00
887664f3c0 v0.3.14-pre.007 2026-09-12 07:47:16 +02:00
ae712eae64 v0.3.14-pre.006 2026-09-11 23:48:53 +02:00
a3df8f6ef5 v0.3.14-pre.005 2026-09-11 23:15:00 +02:00
1006079654 v0.3.14-pre.004 2026-09-11 22:22:51 +02:00
6721a4142a v0.3.14-pre.003 2026-09-11 21:54:19 +02:00
7bf938c19f v0.3.14-pre.002-fix.002 2026-09-11 20:55:53 +02:00
5932250314 v0.3.14-pre.002-fix.001 2026-09-11 20:00:56 +02:00
b2068e9ef2 v0.3.14-pre.002 2026-09-11 19:58:32 +02:00
2ca56dbaf4 v0.3.14-pre.001 2026-09-11 19:37:36 +02:00
63 changed files with 15044 additions and 280 deletions

View File

@@ -1,8 +1,20 @@
<!-- file: CHANGELOG.md --> <!-- file: CHANGELOG.md -->
<!-- version: 33 --> <!-- version: 34 -->
# Changelog KSP # Changelog KSP
## 0.3.14 — gap repair et hardening multi-source du Worker RawTransaction — 2026-09-12
`0.3.14` ferme `ksp-worker-raw-transaction-ingest-lib` par une continuité run-local explicite au-dessus des cinq familles live de `0.3.13`, sans transformer le Worker en moteur historique. Le Worker distingue désormais reconnect, tentative de replay, delivery de replay et preuve de coverage ; il maintient des gaps bornés avec plage inclusive, `TargetCoverage` conservative et frontier de continuité distincte de la processing frontier. Une reprise live ou l'acceptation d'un `from_slot` ne vaut jamais à elle seule preuve de réparation.
La réconciliation peut s'appuyer sur une coverage réellement prouvée d'une source redondante, un scan HTTP borné de blocs/slots ou l'hydration `getTransaction observed` des références connues. Les bornes finales restent 64 gaps ouverts, 4096 slots par plage, fenêtres de discovery de 512 slots, 4 `getBlock` logiques en vol et un seul gap activement réparé. Les matériaux de repair repassent par la même canonicalisation Common RAW, la même admission, la même hydration globale et le même Store ; aucun second pipeline, retry réseau Worker, Job Backfill automatique ou backend Store direct n'est introduit.
La politique multi-source ne fault plus mécaniquement sur toute perte de source : la continuation n'est autorisée que lorsque les gaps concernés sont réconciliés et que la coverage présente/future requise est réellement démontrée. Les snapshots publics ajoutent une observabilité source-neutral et bornée des gaps avec états/raisons/méthodes et compteurs checked, sans source key, endpoint, token, signature, payload ou erreur provider arbitraire. La health policy reste conservative, la fairness alterne trafic nominal/repair sur les capacités existantes et le shutdown borne également le drain interne des sources avec abort+join des siblings non coopératifs.
La fermeture cross-layer confirme le pipeline unique `Transport -> Worker -> ksp-raw-transaction-lib -> Store`, la non-régression Legacy/V0/V1, les dependency firewalls et l'indépendance Worker/Backfill. Le gate technique final passe fmt, audits Rust/Markdown, `cargo check --workspace`, Clippy workspace/all-targets/all-features avec `-D warnings`, `cargo test --workspace --all-targets --all-features` et les graphes Cargo : `1 931` tests passent, `0` échoue et `15` restent ignored opt-in/operator-only. Les smokes keyless Solana HTTP Devnet et WebSocket Devnet passent `1/1` chacun ; Yellowstone/provider-gated, Helius live, PostgreSQL live et Worker->Store end-to-end restent explicitement non exécutés lorsqu'aucune ressource correspondante n'a été provisionnée.
`prompts/034-V0_3_15_START_PROMPT.md` ouvre `0.3.15` exclusivement depuis le futur tag stable `v0.3.14`. Cette release introduit `ksp-app-raw-transaction-ingest-desk` comme Desk Tauri de composition/supervision : une **route** est une stratégie complète pouvant nécessiter plusieurs capacités Config/Transport, elle n'est sélectionnable que si toutes ses requirements sont composables, puis une instance Worker réelle est démarrée par route sélectionnée et valide l'opérabilité runtime au Start. Le frontend reste une surface sûre ; Config, Transport, Store et Worker conservent leurs responsabilités.
## 0.3.13 — convergence live multi-source du Worker RawTransaction — 2026-09-10 ## 0.3.13 — convergence live multi-source du Worker RawTransaction — 2026-09-10
`0.3.13` généralise `ksp-worker-raw-transaction-ingest-lib` de la verticale Yellowstone de `0.3.12` vers une composition caller-owned de `1..32` sources logiques appartenant au même réseau. Cinq familles live convergent désormais vers le même pipeline Common RAW / admission / Store : Yellowstone, Standard Logs avec hydration `getTransaction observed`, Standard Block RAW-direct, Helius `transactionSubscribe` avec hydration et HTTP Block Polling run-local RAW-direct. Le Worker reste sans Config, Job Backfill, backend Store physique, client réseau direct ou SDK provider parallèle. `0.3.13` généralise `ksp-worker-raw-transaction-ingest-lib` de la verticale Yellowstone de `0.3.12` vers une composition caller-owned de `1..32` sources logiques appartenant au même réseau. Cinq familles live convergent désormais vers le même pipeline Common RAW / admission / Store : Yellowstone, Standard Logs avec hydration `getTransaction observed`, Standard Block RAW-direct, Helius `transactionSubscribe` avec hydration et HTTP Block Polling run-local RAW-direct. Le Worker reste sans Config, Job Backfill, backend Store physique, client réseau direct ou SDK provider parallèle.

View File

@@ -1,12 +1,12 @@
# file: Cargo.toml # file: Cargo.toml
# version: 565 # version: 593
[workspace] [workspace]
resolver = "3" resolver = "3"
members = ["crates/ksp-app-backfill-desk", "crates/ksp-app-config-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-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.13" version = "0.3.14"
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: 111 --> <!-- version: 112 -->
# Roadmap KSP # Roadmap KSP
@@ -106,8 +106,8 @@ RAW -> STRUCTURAL -> DECODED -> DOMAIN
- [X] `0.3.11` — Fondation runtime de `ksp-worker-raw-transaction-ingest-lib` livrée : crate/dependency firewall, settings source-neutral, handle/start-stop, lifecycle, snapshots, supervisor, admission bornée, canonicalisation Common RAW, persistence/déduplication Store déterministe, backpressure et shutdown/fault hardening. La release se ferme volontairement sans source réseau productive ; Yellowstone + hydration HTTP commencent en `0.3.12`. - [X] `0.3.11` — Fondation runtime de `ksp-worker-raw-transaction-ingest-lib` livrée : crate/dependency firewall, settings source-neutral, handle/start-stop, lifecycle, snapshots, supervisor, admission bornée, canonicalisation Common RAW, persistence/déduplication Store déterministe, backpressure et shutdown/fault hardening. La release se ferme volontairement sans source réseau productive ; Yellowstone + hydration HTTP commencent en `0.3.12`.
- [X] `0.3.12` — Première source live productive du Worker livrée : Yellowstone `Transaction`/`TransactionStatus`/`Block` vers signaux source-neutral, coalescence bornée puis hydration HTTP `getTransaction` observed avant Common RAW/Store ; `BlockMeta`/`Slot` restent continuity-only. Processing frontier run-local, reconnect/from_slot/ReplayInfo Transport-owned, compteurs source-neutral et fault sur gap de rétention prouvé sans Backfill automatique. Hardening duplicate/backpressure/stop/fault et canaris cross-layer fermés ; gate workspace complet vert, smokes live HTTP Devnet + WebSocket Devnet verts, Yellowstone token-gated/Worker end-to-end laissés explicitement non exécutés. - [X] `0.3.12` — Première source live productive du Worker livrée : Yellowstone `Transaction`/`TransactionStatus`/`Block` vers signaux source-neutral, coalescence bornée puis hydration HTTP `getTransaction` observed avant Common RAW/Store ; `BlockMeta`/`Slot` restent continuity-only. Processing frontier run-local, reconnect/from_slot/ReplayInfo Transport-owned, compteurs source-neutral et fault sur gap de rétention prouvé sans Backfill automatique. Hardening duplicate/backpressure/stop/fault et canaris cross-layer fermés ; gate workspace complet vert, smokes live HTTP Devnet + WebSocket Devnet verts, Yellowstone token-gated/Worker end-to-end laissés explicitement non exécutés.
- [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.
- [ ] `0.3.14`Fermer le Worker par le **gap repair/hardening multi-source** : replay natif, source redondante, scan HTTP blocs, hydration de réparation, unresolved gaps visibles, politiques degraded/unhealthy/faulted, shutdown pendant repair, smokes provider accessibles et adapter EARLY seulement si réellement prouvé. - [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 spécialisée qui choisit et supervise une ou plusieurs sources/méthodes réellement admises par le Worker finalisé : lifecycle, health, rates, backpressure, reconnect/recovery, gap state et compteurs sûrs. La Desk ne réimplémente ni discovery, hydration, déduplication, reprise 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` — É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.
### TODO/IDEAS — applications spécialisées et control plane ### TODO/IDEAS — applications spécialisées et control plane

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-onchain-transport-lib/USAGE.md --> <!-- file: crates/ksp-onchain-transport-lib/USAGE.md -->
<!-- version: 25 --> <!-- version: 26 -->
# Utilisation de `ksp-onchain-transport-lib` # Utilisation de `ksp-onchain-transport-lib`
@@ -387,9 +387,11 @@ let snapshot = stream.snapshot();
let closed = stream.close().await; let closed = stream.close().await;
``` ```
`try_update()` remplace dynamiquement la requête complète tant que la session est `Active`. Une mutation pendant `Reconnecting` est refusée pour éviter une application ambiguë. Le snapshot expose reconnects, replay attempts, gaps, duplicates, dernier `from_slot` demandé et dernier slot observé, sans endpoint ni payload arbitraire. `try_update()` remplace dynamiquement la requête complète tant que la session est `Active`. Une mutation pendant `Reconnecting` est refusée pour éviter une application ambiguë. Le snapshot expose reconnects, replay attempts, replay deliveries conservatrices, reprises dont la couverture reste non prouvée, gaps de rétention, duplicates, dernier `from_slot` demandé et dernier slot observé, sans endpoint ni payload arbitraire.
Le reconnect réutilise la dernière requête acceptée et peut avancer `from_slot`, mais le consumer doit traiter cette reprise comme best-effort. KSP ne promet ni exactly-once, ni replay historique complet, ni absence de fork/equivocation entre nœuds. Une `replay_delivery_count` n'augmente que lorsque le premier flux post-reconnect redélivre exactement le slot de reprise demandé. Elle prouve une livraison au bord de replay, pas la complétude de l'intervalle. Dès que le premier update slot-bearing atteint ou dépasse cette borne, `replay_coverage_unproven_count` augmente aussi : Transport ne possède pas de preuve générique que tous les updates correspondant aux filtres ont été livrés entre la borne et la reprise live. Le compteur augmente également si un nouveau reconnect survient avant tout matériau slot-bearing. Cette valeur signifie explicitement « couverture non prouvée » ; elle ne prouve pas qu'un événement filtré existait ou a été perdu.
Le reconnect réutilise la dernière requête acceptée et peut avancer `from_slot`, mais le consumer doit traiter cette reprise comme best-effort. `replay_attempt_count`, `replay_delivery_count` et une preuve de coverage sont des notions distinctes. Transport ne publie actuellement aucun compteur `replay_covered` générique, car ni l'acceptation de `from_slot` ni une livraison ponctuelle ne démontrent à elles seules une couverture historique complète. KSP ne promet ni exactly-once, ni replay historique complet, ni absence de fork/equivocation entre nœuds.
## 5. Appels typés ## 5. Appels typés

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/grpc_stream.rs // file: crates/ksp-onchain-transport-lib/src/grpc_stream.rs
// version: 4 // version: 5
use tonic_prost::prost::Message; // rust-rules: trait-import use tonic_prost::prost::Message; // rust-rules: trait-import
@@ -34,6 +34,8 @@ pub struct YellowstoneGrpcSubscribeSnapshot {
continuity_gap_count: u64, continuity_gap_count: u64,
duplicate_update_count: u64, duplicate_update_count: u64,
replay_attempt_count: u64, replay_attempt_count: u64,
replay_delivery_count: u64,
replay_coverage_unproven_count: u64,
last_requested_from_slot: std::option::Option<u64>, last_requested_from_slot: std::option::Option<u64>,
last_observed_slot: std::option::Option<u64>, last_observed_slot: std::option::Option<u64>,
terminal_error_code: std::option::Option<ksp_core_lib::ErrorCode>, terminal_error_code: std::option::Option<ksp_core_lib::ErrorCode>,
@@ -84,6 +86,8 @@ impl YellowstoneGrpcSubscribeSnapshot {
continuity_gap_count: 0, continuity_gap_count: 0,
duplicate_update_count: 0, duplicate_update_count: 0,
replay_attempt_count: 0, replay_attempt_count: 0,
replay_delivery_count: 0,
replay_coverage_unproven_count: 0,
last_requested_from_slot: initial_from_slot, last_requested_from_slot: initial_from_slot,
last_observed_slot: std::option::Option::None, last_observed_slot: std::option::Option::None,
terminal_error_code: std::option::Option::None, terminal_error_code: std::option::Option::None,
@@ -122,6 +126,25 @@ impl YellowstoneGrpcSubscribeSnapshot {
return self.replay_attempt_count; return self.replay_attempt_count;
} }
/// Returns the number of replay-bearing reconnects that delivered the requested replay boundary slot again.
///
/// This is conservative delivery evidence only. It does not prove that every matching update in the replay interval was delivered and must never be
/// interpreted as `replay_covered`.
#[must_use]
pub const fn replay_delivery_count(self) -> u64 {
return self.replay_delivery_count;
}
/// Returns the number of successful replay-bearing reconnects whose target coverage remains unproven.
///
/// The counter advances when the first post-reconnect slot-bearing update reaches or passes the requested replay boundary, because generic Transport
/// cannot prove from that delivery alone that every matching update in the replay interval was delivered. It also advances when another reconnect starts
/// before any slot-bearing replay material arrives. This is an explicit lack of coverage proof, not proof that a filtered event actually existed or was lost.
#[must_use]
pub const fn replay_coverage_unproven_count(self) -> u64 {
return self.replay_coverage_unproven_count;
}
/// Returns the most recent effective `from_slot` sent by KSP, including any clamp to `SubscribeReplayInfo.first_available`. /// Returns the most recent effective `from_slot` sent by KSP, including any clamp to `SubscribeReplayInfo.first_available`.
#[must_use] #[must_use]
pub const fn last_requested_from_slot(self) -> std::option::Option<u64> { pub const fn last_requested_from_slot(self) -> std::option::Option<u64> {
@@ -506,17 +529,44 @@ enum UpdateIdentity {
} }
struct ContinuityTracker { struct ContinuityTracker {
pending_replay_from_slot: std::option::Option<u64>,
recent_order: std::collections::VecDeque<UpdateIdentity>, recent_order: std::collections::VecDeque<UpdateIdentity>,
recent_set: std::collections::HashSet<UpdateIdentity>, recent_set: std::collections::HashSet<UpdateIdentity>,
} }
impl ContinuityTracker { impl ContinuityTracker {
fn new() -> Self { fn new() -> Self {
return Self { recent_order: std::collections::VecDeque::new(), recent_set: std::collections::HashSet::new() }; return Self {
pending_replay_from_slot: std::option::Option::None,
recent_order: std::collections::VecDeque::new(),
recent_set: std::collections::HashSet::new(),
};
}
fn begin_replay(&mut self, from_slot: std::option::Option<u64>) {
self.pending_replay_from_slot = from_slot;
return;
}
fn abandon_pending_replay(&mut self, snapshot: &mut crate::YellowstoneGrpcSubscribeSnapshot) -> bool {
if self.pending_replay_from_slot.take().is_none() {
return false;
}
snapshot.replay_coverage_unproven_count = snapshot.replay_coverage_unproven_count.saturating_add(1);
return true;
} }
fn observe(&mut self, update: &crate::YellowstoneSubscribeUpdate, snapshot: &mut crate::YellowstoneGrpcSubscribeSnapshot) { fn observe(&mut self, update: &crate::YellowstoneSubscribeUpdate, snapshot: &mut crate::YellowstoneGrpcSubscribeSnapshot) {
if let std::option::Option::Some(slot) = update_slot(update) { if let std::option::Option::Some(slot) = update_slot(update) {
if let std::option::Option::Some(requested) = self.pending_replay_from_slot
&& slot >= requested
{
if slot == requested {
snapshot.replay_delivery_count = snapshot.replay_delivery_count.saturating_add(1);
}
snapshot.replay_coverage_unproven_count = snapshot.replay_coverage_unproven_count.saturating_add(1);
self.pending_replay_from_slot = std::option::Option::None;
}
snapshot.last_observed_slot = std::option::Option::Some(match snapshot.last_observed_slot { snapshot.last_observed_slot = std::option::Option::Some(match snapshot.last_observed_slot {
std::option::Option::Some(previous) => std::cmp::max(previous, slot), std::option::Option::Some(previous) => std::cmp::max(previous, slot),
std::option::Option::None => slot, std::option::Option::None => slot,
@@ -620,6 +670,7 @@ async fn run_subscribe_actor(
&mut shutdown_rx, &mut shutdown_rx,
&mut snapshot, &mut snapshot,
&snapshot_tx, &snapshot_tx,
&mut tracker,
) )
.await .await
{ {
@@ -662,6 +713,7 @@ async fn run_subscribe_actor(
&mut shutdown_rx, &mut shutdown_rx,
&mut snapshot, &mut snapshot,
&snapshot_tx, &snapshot_tx,
&mut tracker,
) )
.await .await
{ {
@@ -701,8 +753,12 @@ async fn reconnect_subscribe_stream(
shutdown_rx: &mut tokio::sync::watch::Receiver<std::option::Option<tokio::time::Instant>>, shutdown_rx: &mut tokio::sync::watch::Receiver<std::option::Option<tokio::time::Instant>>,
snapshot: &mut crate::YellowstoneGrpcSubscribeSnapshot, snapshot: &mut crate::YellowstoneGrpcSubscribeSnapshot,
snapshot_tx: &tokio::sync::watch::Sender<crate::YellowstoneGrpcSubscribeSnapshot>, snapshot_tx: &tokio::sync::watch::Sender<crate::YellowstoneGrpcSubscribeSnapshot>,
tracker: &mut ContinuityTracker,
) -> ReconnectOutcome { ) -> ReconnectOutcome {
clear_request_sender(request_state); clear_request_sender(request_state);
if tracker.abandon_pending_replay(snapshot) {
snapshot_tx.send_replace(*snapshot);
}
snapshot.state = crate::YellowstoneGrpcSubscribeState::Reconnecting; snapshot.state = crate::YellowstoneGrpcSubscribeState::Reconnecting;
snapshot.terminal_error_code = std::option::Option::None; snapshot.terminal_error_code = std::option::Option::None;
snapshot_tx.send_replace(*snapshot); snapshot_tx.send_replace(*snapshot);
@@ -785,6 +841,7 @@ async fn reconnect_subscribe_stream(
return ReconnectOutcome::Exhausted(error); return ReconnectOutcome::Exhausted(error);
} }
snapshot.reconnect_count = snapshot.reconnect_count.saturating_add(1); snapshot.reconnect_count = snapshot.reconnect_count.saturating_add(1);
tracker.begin_replay(effective_from_slot);
snapshot.state = crate::YellowstoneGrpcSubscribeState::Active; snapshot.state = crate::YellowstoneGrpcSubscribeState::Active;
snapshot.terminal_error_code = std::option::Option::None; snapshot.terminal_error_code = std::option::Option::None;
snapshot_tx.send_replace(*snapshot); snapshot_tx.send_replace(*snapshot);

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/lib.rs // file: crates/ksp-onchain-transport-lib/src/lib.rs
// version: 47 // version: 48
#![warn(missing_docs)] #![warn(missing_docs)]
#![deny(unreachable_pub)] #![deny(unreachable_pub)]
@@ -555,6 +555,8 @@ pub use self::ws_protocol_session::HeliusLaserStreamWsSession;
pub use self::ws_protocol_session::SolanaStandardWsSession; pub use self::ws_protocol_session::SolanaStandardWsSession;
/// Shareable compatibility handle for one explicitly created standard Solana physical WebSocket session. /// Shareable compatibility handle for one explicitly created standard Solana physical WebSocket session.
pub use self::ws_session::WsSession; pub use self::ws_session::WsSession;
/// Cloneable latest-value observer for one safe physical WebSocket session snapshot.
pub use self::ws_session::WsSessionSnapshotSource;
/// Open cluster or network descriptor used by WebSocket endpoint settings. /// Open cluster or network descriptor used by WebSocket endpoint settings.
pub use self::ws_settings::WsClusterName; pub use self::ws_settings::WsClusterName;
/// Runtime settings for one named WebSocket endpoint. /// Runtime settings for one named WebSocket endpoint.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/ws_protocol_session.rs // file: crates/ksp-onchain-transport-lib/src/ws_protocol_session.rs
// version: 6 // version: 7
/// Typed facade for one standard Solana WebSocket physical session. /// Typed facade for one standard Solana WebSocket physical session.
/// ///
@@ -42,6 +42,12 @@ impl SolanaStandardWsSession {
return self.inner.snapshot(); return self.inner.snapshot();
} }
/// Returns a cloneable latest-value observer for safe physical-session snapshots from the shared actor.
#[must_use]
pub fn snapshot_source(&self) -> crate::WsSessionSnapshotSource {
return self.inner.snapshot_source();
}
/// Returns the latest observable physical-session state. /// Returns the latest observable physical-session state.
#[must_use] #[must_use]
pub fn state(&self) -> crate::WsSessionState { pub fn state(&self) -> crate::WsSessionState {
@@ -117,6 +123,12 @@ impl HeliusLaserStreamWsSession {
return self.inner.snapshot(); return self.inner.snapshot();
} }
/// Returns a cloneable latest-value observer for safe physical-session snapshots from the shared actor.
#[must_use]
pub fn snapshot_source(&self) -> crate::WsSessionSnapshotSource {
return self.inner.snapshot_source();
}
/// Returns the latest observable physical-session state. /// Returns the latest observable physical-session state.
#[must_use] #[must_use]
pub fn state(&self) -> crate::WsSessionState { pub fn state(&self) -> crate::WsSessionState {

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/src/ws_session.rs // file: crates/ksp-onchain-transport-lib/src/ws_session.rs
// version: 14 // version: 15
use futures_util::SinkExt; // rust-rules: trait-import use futures_util::SinkExt; // rust-rules: trait-import
use futures_util::StreamExt; // rust-rules: trait-import use futures_util::StreamExt; // rust-rules: trait-import
@@ -10,6 +10,43 @@ const HELIUS_WS_HEARTBEAT_INTERVAL: std::time::Duration = std::time::Duration::f
type WsPhysicalStream = tokio_tungstenite::WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>; type WsPhysicalStream = tokio_tungstenite::WebSocketStream<tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>>;
/// Cloneable latest-value observer for one physical WebSocket session snapshot.
///
/// The observer exposes only the already-safe [`crate::WsSessionSnapshot`] projection and keeps the internal Tokio watch channel private. Cloning it does
/// not create another socket, actor, reconnect loop or subscription registry.
#[derive(Clone)]
pub struct WsSessionSnapshotSource {
receiver: tokio::sync::watch::Receiver<crate::WsSessionSnapshot>,
}
impl crate::WsSessionSnapshotSource {
fn new(receiver: tokio::sync::watch::Receiver<crate::WsSessionSnapshot>) -> Self {
return Self { receiver };
}
/// Returns the current safe physical-session snapshot without waiting for another actor transition.
#[must_use]
pub fn current(&self) -> crate::WsSessionSnapshot {
return (*self.receiver.borrow()).clone();
}
/// Waits for one newer safe physical-session snapshot.
///
/// `None` means the owning WebSocket actor dropped the latest-value publisher and no further snapshot can arrive.
pub async fn wait_for_change(&mut self) -> std::option::Option<crate::WsSessionSnapshot> {
return match self.receiver.changed().await {
std::result::Result::Ok(()) => std::option::Option::Some((*self.receiver.borrow_and_update()).clone()),
std::result::Result::Err(_) => std::option::Option::None,
};
}
}
impl std::fmt::Debug for crate::WsSessionSnapshotSource {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter.debug_struct("WsSessionSnapshotSource").field("current", &self.current()).finish();
}
}
/// Shareable handle for one explicitly created physical WebSocket session. /// Shareable handle for one explicitly created physical WebSocket session.
/// ///
/// The handle never exposes the sensitive endpoint URL or the underlying socket. All socket I/O is owned by one internal actor task and all caller /// The handle never exposes the sensitive endpoint URL or the underlying socket. All socket I/O is owned by one internal actor task and all caller
@@ -124,6 +161,12 @@ impl WsSession {
return self.snapshot_rx.borrow().clone(); return self.snapshot_rx.borrow().clone();
} }
/// Returns a cloneable latest-value observer for safe physical-session snapshots.
#[must_use]
pub fn snapshot_source(&self) -> crate::WsSessionSnapshotSource {
return crate::WsSessionSnapshotSource::new(self.snapshot_rx.clone());
}
/// Returns the latest observable physical-session state. /// Returns the latest observable physical-session state.
#[must_use] #[must_use]
pub fn state(&self) -> crate::WsSessionState { pub fn state(&self) -> crate::WsSessionState {

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/tests/public_api.rs // file: crates/ksp-onchain-transport-lib/tests/public_api.rs
// version: 52 // version: 54
//! Integration tests for the public `ksp-onchain-transport-lib` consumer contract. //! Integration tests for the public `ksp-onchain-transport-lib` consumer contract.
@@ -569,6 +569,17 @@ fn public_v0_2_7_pre_004_physical_websocket_session_contract_is_available_from_c
assert_eq!(ksp_onchain_transport_lib::ERROR_CODE_WS_SESSION_CLOSED.code(), "ws_session_closed"); assert_eq!(ksp_onchain_transport_lib::ERROR_CODE_WS_SESSION_CLOSED.code(), "ws_session_closed");
} }
#[test]
fn public_v0_3_14_pre_003_websocket_snapshot_source_is_available_from_crate_root() {
let _physical = ksp_onchain_transport_lib::WsSession::snapshot_source;
let _standard = ksp_onchain_transport_lib::SolanaStandardWsSession::snapshot_source;
let _helius = ksp_onchain_transport_lib::HeliusLaserStreamWsSession::snapshot_source;
let _current = ksp_onchain_transport_lib::WsSessionSnapshotSource::current;
let _wait = ksp_onchain_transport_lib::WsSessionSnapshotSource::wait_for_change;
assert!(std::any::type_name::<ksp_onchain_transport_lib::WsSessionSnapshotSource>().ends_with("WsSessionSnapshotSource"));
return;
}
#[test] #[test]
fn public_v0_2_7_pre_005_bounded_websocket_close_contract_is_available_from_crate_root() { fn public_v0_2_7_pre_005_bounded_websocket_close_contract_is_available_from_crate_root() {
let _close = ksp_onchain_transport_lib::WsSession::close; let _close = ksp_onchain_transport_lib::WsSession::close;
@@ -1021,6 +1032,8 @@ fn public_v0_2_9_pre_010_yellowstone_reconnect_snapshot_is_available_from_crate_
let _gap_count = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot::continuity_gap_count; let _gap_count = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot::continuity_gap_count;
let _duplicate_count = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot::duplicate_update_count; let _duplicate_count = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot::duplicate_update_count;
let _replay_count = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot::replay_attempt_count; let _replay_count = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot::replay_attempt_count;
let _delivery_count = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot::replay_delivery_count;
let _coverage_unproven_count = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot::replay_coverage_unproven_count;
let _requested = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot::last_requested_from_slot; let _requested = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot::last_requested_from_slot;
let _observed = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot::last_observed_slot; let _observed = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot::last_observed_slot;
let _terminal = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot::terminal_error_code; let _terminal = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot::terminal_error_code;

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs // file: crates/ksp-onchain-transport-lib/tests/release_completeness.rs
// version: 44 // version: 46
//! Release-level completeness canaries for staged HTTP and WebSocket Transport coverage. //! Release-level completeness canaries for staged HTTP and WebSocket Transport coverage.
@@ -1354,3 +1354,48 @@ fn release_v0_3_13_pre_002_yellowstone_subscribe_identity_remains_opaque_and_dep
assert!(!root.contains("pub use yellowstone_grpc_proto")); assert!(!root.contains("pub use yellowstone_grpc_proto"));
return; return;
} }
#[test]
fn release_v0_3_14_pre_003_ws_snapshot_source_reuses_actor_watch_without_second_runtime() {
let session_source = include_str!("../src/ws_session.rs");
let facade_source = include_str!("../src/ws_protocol_session.rs");
let session_tests = include_str!("../unit_tests/ws_session.rs");
assert!(session_source.contains("pub struct WsSessionSnapshotSource"));
assert!(session_source.contains("self.snapshot_rx.clone()"));
assert!(facade_source.matches("pub fn snapshot_source").count() >= 2);
for required in [
"v0_3_14_pre_003_snapshot_source_tracks_latest_value_without_owning_runtime",
"websocket_reconnect_resubscribes_in_local_id_order_and_remaps_remote_ids",
"websocket_notification_queue_overflow_fails_only_slow_subscription_and_cleans_remote_binding",
] {
assert!(session_tests.contains(required), "missing pre.003 WebSocket continuity test: {required}");
}
assert!(!session_source.contains("WsSessionSnapshotSource {\n socket:"));
assert!(!facade_source.contains("tokio_tungstenite::connect_async"));
return;
}
#[test]
fn release_v0_3_14_pre_004_native_replay_evidence_never_conflates_attempt_delivery_and_coverage() {
let stream_source = include_str!("../src/grpc_stream.rs");
let stream_tests = include_str!("../unit_tests/grpc_stream.rs");
for required in [
"replay_attempt_count",
"replay_delivery_count",
"replay_coverage_unproven_count",
"pending_replay_from_slot",
"begin_replay",
"abandon_pending_replay",
"first_available > requested",
"This is conservative delivery evidence only",
"explicit lack of coverage proof",
] {
assert!(stream_source.contains(required), "missing pre.004 native replay evidence token: {required}");
}
assert!(stream_tests.contains("v0_3_14_pre_004_replay_acceptance_without_boundary_delivery_never_claims_coverage"));
assert!(stream_tests.contains("yellowstone_reconnect_replays_from_last_observed_slot_and_counts_duplicate_identity"));
assert!(stream_tests.contains("yellowstone_replay_info_proves_and_clamps_retention_gap_without_lossless_claim"));
assert!(!stream_source.contains("replay_covered_count"));
assert!(!stream_source.contains("replay_coverage_proven_count"));
return;
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/unit_tests/grpc_stream.rs // file: crates/ksp-onchain-transport-lib/unit_tests/grpc_stream.rs
// version: 5 // version: 6
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
enum FixtureMode { enum FixtureMode {
@@ -13,6 +13,7 @@ enum FixtureMode {
Idle, Idle,
ReconnectReplay, ReconnectReplay,
ReplayGap, ReplayGap,
ReplayAcceptedWithoutBoundary,
ReconnectExhausted, ReconnectExhausted,
} }
@@ -153,6 +154,22 @@ impl yellowstone_grpc_proto::geyser::geyser_server::Geyser for FixtureGeyser {
} }
} }
}, },
FixtureMode::ReplayAcceptedWithoutBoundary => {
if subscribe_call == 1 {
assert_eq!(initial.from_slot, std::option::Option::None);
let _ = outbound_tx.send(std::result::Result::Ok(slot_update(800))).await;
} else {
assert_eq!(subscribe_call, 2);
assert_eq!(initial.from_slot, std::option::Option::Some(800));
if outbound_tx.send(std::result::Result::Ok(slot_update(805))).await.is_err() {
return;
}
let half_close = inbound.message().await;
if matches!(half_close, std::result::Result::Ok(std::option::Option::None)) {
half_close_seen.store(true, std::sync::atomic::Ordering::SeqCst);
}
}
},
FixtureMode::ReconnectExhausted => { FixtureMode::ReconnectExhausted => {
assert_eq!(subscribe_call, 1); assert_eq!(subscribe_call, 1);
let _ = outbound_tx.send(std::result::Result::Ok(slot_update(700))).await; let _ = outbound_tx.send(std::result::Result::Ok(slot_update(700))).await;
@@ -191,7 +208,7 @@ impl yellowstone_grpc_proto::geyser::geyser_server::Geyser for FixtureGeyser {
return std::result::Result::Err(error); return std::result::Result::Err(error);
} }
let first_available = match self.mode { let first_available = match self.mode {
FixtureMode::ReconnectReplay | FixtureMode::ReconnectExhausted => std::option::Option::Some(400), FixtureMode::ReconnectReplay | FixtureMode::ReconnectExhausted | FixtureMode::ReplayAcceptedWithoutBoundary => std::option::Option::Some(400),
FixtureMode::ReplayGap => std::option::Option::Some(505), FixtureMode::ReplayGap => std::option::Option::Some(505),
_ => return std::result::Result::Err(tonic::Status::unimplemented("replay info is outside this fixture mode")), _ => return std::result::Result::Err(tonic::Status::unimplemented("replay info is outside this fixture mode")),
}; };
@@ -595,6 +612,8 @@ async fn yellowstone_reconnect_replays_from_last_observed_slot_and_counts_duplic
assert_eq!(snapshot.state(), crate::YellowstoneGrpcSubscribeState::Active); assert_eq!(snapshot.state(), crate::YellowstoneGrpcSubscribeState::Active);
assert_eq!(snapshot.reconnect_count(), 1); assert_eq!(snapshot.reconnect_count(), 1);
assert_eq!(snapshot.replay_attempt_count(), 1); assert_eq!(snapshot.replay_attempt_count(), 1);
assert_eq!(snapshot.replay_delivery_count(), 1);
assert_eq!(snapshot.replay_coverage_unproven_count(), 1);
assert_eq!(snapshot.continuity_gap_count(), 0); assert_eq!(snapshot.continuity_gap_count(), 0);
assert_eq!(snapshot.duplicate_update_count(), 1); assert_eq!(snapshot.duplicate_update_count(), 1);
assert_eq!(snapshot.last_requested_from_slot(), std::option::Option::Some(500)); assert_eq!(snapshot.last_requested_from_slot(), std::option::Option::Some(500));
@@ -627,6 +646,8 @@ async fn yellowstone_replay_info_proves_and_clamps_retention_gap_without_lossles
let snapshot = session.snapshot(); let snapshot = session.snapshot();
assert_eq!(snapshot.reconnect_count(), 1); assert_eq!(snapshot.reconnect_count(), 1);
assert_eq!(snapshot.replay_attempt_count(), 1); assert_eq!(snapshot.replay_attempt_count(), 1);
assert_eq!(snapshot.replay_delivery_count(), 1);
assert_eq!(snapshot.replay_coverage_unproven_count(), 1);
assert_eq!(snapshot.continuity_gap_count(), 1); assert_eq!(snapshot.continuity_gap_count(), 1);
assert_eq!(snapshot.duplicate_update_count(), 0); assert_eq!(snapshot.duplicate_update_count(), 0);
assert_eq!(snapshot.last_requested_from_slot(), std::option::Option::Some(505)); assert_eq!(snapshot.last_requested_from_slot(), std::option::Option::Some(505));
@@ -635,6 +656,42 @@ async fn yellowstone_replay_info_proves_and_clamps_retention_gap_without_lossles
server.stop().await; server.stop().await;
} }
#[tokio::test(flavor = "current_thread")]
async fn v0_3_14_pre_004_replay_acceptance_without_boundary_delivery_never_claims_coverage() {
let server = FixtureServer::start(FixtureMode::ReplayAcceptedWithoutBoundary).await;
let defaults = crate::YellowstoneGrpcSessionSettings::default();
let settings = fixture_settings_with_reconnect(
server.endpoint_url.as_str(),
8,
8,
defaults.max_inbound_message_size_bytes(),
defaults.max_outbound_message_size_bytes(),
crate::YellowstoneGrpcReconnectSettings::new(3, std::time::Duration::from_millis(5), std::time::Duration::from_millis(20)),
);
let channel = crate::YellowstoneGrpcChannel::connect(&settings).await.expect("fixture channel must connect");
let mut session = channel.open_standard_subscribe(initial_request()).await.expect("fixture Subscribe stream must open");
let first = session.next_update().await.expect("first slot must decode").expect("first slot must be present");
match first {
crate::YellowstoneSubscribeUpdate::Slot(value) => assert_eq!(value.slot(), 800),
_ => panic!("fixture must return first Slot"),
}
let resumed = session.next_update().await.expect("post-reconnect slot must decode").expect("post-reconnect slot must be present");
match resumed {
crate::YellowstoneSubscribeUpdate::Slot(value) => assert_eq!(value.slot(), 805),
_ => panic!("fixture must return post-reconnect Slot"),
}
let snapshot = session.snapshot();
assert_eq!(snapshot.reconnect_count(), 1);
assert_eq!(snapshot.replay_attempt_count(), 1);
assert_eq!(snapshot.replay_delivery_count(), 0);
assert_eq!(snapshot.replay_coverage_unproven_count(), 1);
assert_eq!(snapshot.continuity_gap_count(), 0);
assert_eq!(snapshot.last_requested_from_slot(), std::option::Option::Some(800));
assert_eq!(snapshot.last_observed_slot(), std::option::Option::Some(805));
session.close().await.expect("coverage-unproven fixture must close cleanly");
server.stop().await;
}
#[tokio::test(flavor = "current_thread")] #[tokio::test(flavor = "current_thread")]
async fn yellowstone_reconnect_budget_exhaustion_is_terminal_and_safe() { async fn yellowstone_reconnect_budget_exhaustion_is_terminal_and_safe() {
let server = FixtureServer::start(FixtureMode::ReconnectExhausted).await; let server = FixtureServer::start(FixtureMode::ReconnectExhausted).await;
@@ -656,6 +713,8 @@ async fn yellowstone_reconnect_budget_exhaustion_is_terminal_and_safe() {
assert_eq!(snapshot.state(), crate::YellowstoneGrpcSubscribeState::Failed); assert_eq!(snapshot.state(), crate::YellowstoneGrpcSubscribeState::Failed);
assert_eq!(snapshot.reconnect_count(), 0); assert_eq!(snapshot.reconnect_count(), 0);
assert_eq!(snapshot.replay_attempt_count(), 2); assert_eq!(snapshot.replay_attempt_count(), 2);
assert_eq!(snapshot.replay_delivery_count(), 0);
assert_eq!(snapshot.replay_coverage_unproven_count(), 0);
assert_eq!(snapshot.terminal_error_code(), std::option::Option::Some(crate::ERROR_CODE_GRPC_CHANNEL_FAILED)); assert_eq!(snapshot.terminal_error_code(), std::option::Option::Some(crate::ERROR_CODE_GRPC_CHANNEL_FAILED));
let rendered = format!("{error:?} {session:?}"); let rendered = format!("{error:?} {session:?}");
assert!(!rendered.contains("GRPC-RECONNECT-SECRET-CANARY")); assert!(!rendered.contains("GRPC-RECONNECT-SECRET-CANARY"));

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-onchain-transport-lib/unit_tests/ws_session.rs // file: crates/ksp-onchain-transport-lib/unit_tests/ws_session.rs
// version: 11 // version: 12
use futures_util::SinkExt; // rust-rules: trait-import use futures_util::SinkExt; // rust-rules: trait-import
use futures_util::StreamExt; // rust-rules: trait-import use futures_util::StreamExt; // rust-rules: trait-import
@@ -1355,3 +1355,40 @@ async fn websocket_dropped_notification_receiver_triggers_remote_cleanup_and_rel
session.close().await.expect("session close must remain bounded"); session.close().await.expect("session close must remain bounded");
server.await.expect("local server task must complete"); server.await.expect("local server task must complete");
} }
#[tokio::test(flavor = "current_thread")]
async fn v0_3_14_pre_003_snapshot_source_tracks_latest_value_without_owning_runtime() {
let session_id = crate::WsSessionId::new(std::num::NonZeroU64::new(1).expect("test session ID must be non-zero"));
let initial = crate::WsSessionSnapshot::new(
session_id,
"local_ws",
crate::WsProviderName::new("local-fixture"),
crate::WsClusterName::new("local"),
crate::WsProtocolKind::SolanaStandard,
crate::WsSessionState::Active,
0,
0,
0,
std::vec::Vec::new(),
);
let reconnecting = crate::WsSessionSnapshot::new(
session_id,
"local_ws",
crate::WsProviderName::new("local-fixture"),
crate::WsClusterName::new("local"),
crate::WsProtocolKind::SolanaStandard,
crate::WsSessionState::Reconnecting { attempt: 1 },
0,
1,
0,
std::vec::Vec::new(),
);
let (sender, receiver) = tokio::sync::watch::channel(initial.clone());
let mut source = crate::WsSessionSnapshotSource::new(receiver);
assert_eq!(source.current(), initial);
sender.send_replace(reconnecting.clone());
assert_eq!(source.wait_for_change().await, std::option::Option::Some(reconnecting));
drop(sender);
assert_eq!(source.wait_for_change().await, std::option::Option::None);
return;
}

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-worker-raw-transaction-ingest-lib/README.md --> <!-- file: crates/ksp-worker-raw-transaction-ingest-lib/README.md -->
<!-- version: 11 --> <!-- version: 12 -->
# ksp-worker-raw-transaction-ingest-lib # ksp-worker-raw-transaction-ingest-lib
@@ -84,7 +84,7 @@ La construction est sans I/O et refuse notamment :
Le runtime-resource aggregate public accepte une collection validée de 1 à 32 sources logiques et les démarre simultanément sous un supervisor privé. La collection est validée entièrement avant spawn ; aucun sous-ensemble silencieux, source primaire implicite ou standby n'est choisi. La collection interne, les `source_key`, les URLs, les filtres et les clients inférieurs ne sont pas exposés. Le runtime-resource aggregate public accepte une collection validée de 1 à 32 sources logiques et les démarre simultanément sous un supervisor privé. La collection est validée entièrement avant spawn ; aucun sous-ensemble silencieux, source primaire implicite ou standby n'est choisi. La collection interne, les `source_key`, les URLs, les filtres et les clients inférieurs ne sont pas exposés.
Le supervisor possède toutes les tâches source. Une source qui échoue est terminale pour le Worker et déclenche l'arrêt coopératif puis le join des autres sources, car cette release ne suppose aucune équivalence de coverage. Une fermeture propre d'une source alors que le Worker n'est pas en arrêt est également traitée comme une perte de source configurée et devient terminale. Le supervisor possède toutes les tâches source. Une perte de source n'est plus assimilée automatiquement à un fault : lorsqu'elle porte une plage de continuité bornée, le Worker l'inscrit dans son ledger run-local puis n'autorise la continuation des siblings que si la coverage passée de cette perte est réconciliée et si les sources encore actives couvrent explicitement tout le `TargetCoverage` futur. Une perte sans plage sûre, une coverage insuffisante ou un gap encore ouvert reste terminal. Le Worker ne respawn jamais lui-même une source Transport.
Un inventaire privé `source_key -> latest processing/source state`, borné à 32 entrées, agrège la projection run-local. La frontier agrégée reste conservative : elle n'expose un `processing_frontier_slot` que lorsque toutes les sources en possèdent un, choisit le minimum des frontiers connus et le plus ancien pending. Les sources reference-bearing partagent en plus un registre global d'hydration borné : une même clé `(network, signature, commitment)` ne déclenche qu'un leader HTTP, puis chaque signal source conserve sa propre provenance lors de la finalisation. Un inventaire privé `source_key -> latest processing/source state`, borné à 32 entrées, agrège la projection run-local. La frontier agrégée reste conservative : elle n'expose un `processing_frontier_slot` que lorsque toutes les sources en possèdent un, choisit le minimum des frontiers connus et le plus ancien pending. Les sources reference-bearing partagent en plus un registre global d'hydration borné : une même clé `(network, signature, commitment)` ne déclenche qu'un leader HTTP, puis chaque signal source conserve sa propre provenance lors de la finalisation.
@@ -187,6 +187,8 @@ Les signaux partageant le même `(network, signature, commitment)` sont coalesc
Les retries/reroutages HTTP appartiennent à `ksp-onchain-transport-lib`. Le Worker ne possède pas une seconde boucle de retry autour de `getTransaction`. Les retries/reroutages HTTP appartiennent à `ksp-onchain-transport-lib`. Le Worker ne possède pas une seconde boucle de retry autour de `getTransaction`.
Le trafic nominal et le trafic de réparation partagent le même registre global d'hydration, les mêmes permits, la même admission et la même persistence. Un gate de fairness privé alterne les deux classes lorsqu'elles attendent simultanément, sans réserver une fraction fixe de capacité ; une capacité existante de `1` doit donc encore permettre la progression des deux classes.
## Processing frontier run-local ## Processing frontier run-local
Le snapshot expose : Le snapshot expose :
@@ -209,11 +211,41 @@ getTransaction -> Available puis ingress envoyé avec succès vers l'admission c
Un `BlockMeta` ou `Slot` continuity-only est settled localement sans produire de RAW. La frontier n'avance jamais à travers le plus ancien pending connu. Un `BlockMeta` ou `Slot` continuity-only est settled localement sans produire de RAW. La frontier n'avance jamais à travers le plus ancien pending connu.
## Reconnect, replay et continuité ## Reconnect, replay, gaps et réparation run-local
Le reconnect/replay Yellowstone appartient à Transport. Le Worker n'écrit pas `from_slot` et n'interprète pas directement `SubscribeReplayInfo`. Le reconnect/replay Yellowstone appartient à Transport. Le Worker n'écrit pas `from_slot`, ne traite pas directement `SubscribeReplayInfo` et ne transforme jamais une simple reconnexion en preuve de continuité.
Le snapshot Worker projette uniquement des informations sûres et source-neutral : Le Worker maintient séparément la processing frontier et une continuity frontier gap-aware. Les gaps sont des intervalles inclusifs bornés du run courant ; ils ne constituent ni une campagne historique ni une liste présumée de transactions manquantes. Les bornes internes sont :
```text
open gaps <= 64
range d'un gap <= 4096 slots
discovery HTTP par fenêtre <= 512 slots
getBlock logiques concurrents <= 4
repair actif simultané <= 1
```
Les mécanismes admissibles restent conservatifs : replay Transport lorsqu'il est réellement adressable, preuve de coverage d'une autre source compatible, scan HTTP borné, récupération de bloc produit et hydration d'une référence connue. Une réponse `getTransaction = null` reste une obligation manquante et `getBlock = null` pour un slot prouvé produit ne devient jamais une preuve d'absence.
Les types publics source-neutral `RawTransactionIngestGapId`, `RawTransactionIngestGapState`, `RawTransactionIngestGapReason`, `RawTransactionIngestRepairMethod` et `RawTransactionIngestGapSnapshot` permettent d'observer les gaps sans exposer `source_key`, provider, endpoint, filtre, signature ou payload. Le snapshot concret expose notamment :
```text
gaps()
open_gap_count()
repairing_gap_count()
repaired_gap_total()
unresolved_gap_total()
replay_repair_total()
redundant_coverage_repair_total()
http_scan_repair_total()
repair_block_fetch_total()
repair_transaction_hydration_total()
oldest_open_gap_start_slot()
```
La liste détaillée reste bornée ; tous les gaps ouverts sont retenus et les entrées réparées récentes peuvent occuper la capacité restante. Les compteurs utilisent une arithmétique checked.
Le snapshot conserve également les informations source-neutral suivantes :
```text ```text
source_total source_total
@@ -228,21 +260,11 @@ source_replay_attempt_total
source_continuity_gap_total source_continuity_gap_total
``` ```
Les quatre comptes de sources sont des gauges latest-value et ne contiennent aucune identité de source. `source_total` reste le nombre de sources logiques configurées pour le run. Tant que toutes les sources attendues ne sont pas `Active`, la health publique reste conservative : un reconnect transitoire projette `Degraded`, une source `Failed` projette `Unhealthy`, et le retour de toutes les sources attendues à `Active` permet de revenir à `Healthy`. `RawTransactionIngestSourceState` distingue `Active`, `Reconnecting`, `Closing`, `Closed` et `Failed`. Un replay attempt, une redelivery de frontière et une coverage d'intervalle restent des preuves différentes.
`RawTransactionIngestSourceState` distingue : La health publique est volontairement stricte dès que la policy de continuité est active : reconnect en cours, gap ouvert, continuity frontier différente de la processing frontier ou `TargetCoverage` futur non couvert donnent `Unhealthy`. `Healthy` exige toutes les sources attendues actives et aucune lacune de continuité. `Degraded` n'est permis qu'après perte de source explicitement réconciliée, lorsque les sources restantes couvrent encore tout le `TargetCoverage` futur.
```text Une source perdue peut donc rester absente uniquement si sa perte est bornée, si ses gaps sont fermés, si la continuity frontier rejoint la processing frontier et si la coverage future reste prouvée. Dans tous les autres cas, le Worker fault avec une erreur source sûre. Il ne lance jamais `ksp-job-backfill-lib` ni une campagne historique automatique.
Active
Reconnecting
Closing
Closed
Failed
```
Un replay attempt n'est pas une preuve de replay réussi ni de continuité parfaite. Si Transport augmente son compteur de continuity gap parce que la borne de rétention prouve que le slot demandé n'est plus rejouable, le Worker classe la source en failure et termine avec `worker_raw_transaction_ingest.source_failed`.
Le Worker ne lance alors ni Job Backfill ni campagne historique automatique.
## Persistence Store ## Persistence Store

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-worker-raw-transaction-ingest-lib/USAGE.md --> <!-- file: crates/ksp-worker-raw-transaction-ingest-lib/USAGE.md -->
<!-- version: 11 --> <!-- version: 12 -->
# Utilisation de ksp-worker-raw-transaction-ingest-lib # Utilisation de ksp-worker-raw-transaction-ingest-lib
@@ -117,7 +117,7 @@ if let std::result::Result::Err(error) = resources.try_push_http_block_polling_s
} }
``` ```
La validation est transactionnelle à chaque ajout : limite globale 32, réseau unique et `source_key` logique unique. Au démarrage, toutes les sources présentes sont supervisées ensemble. La défaillance d'une source est terminale pour le Worker ; les autres sources sont arrêtées et jointes, car le Worker ne suppose pas qu'elles couvrent les mêmes filtres ou le même univers de transactions. La validation est transactionnelle à chaque ajout : limite globale 32, réseau unique et `source_key` logique unique. Au démarrage, toutes les sources présentes sont supervisées ensemble. Une défaillance de source ne permet la continuation des siblings que lorsqu'une plage de perte sûre est disponible, que cette perte est entièrement réconciliée et que les sources encore actives prouvent tout le `TargetCoverage` futur. Sinon le Worker arrête et joint les autres sources. Aucune équivalence n'est déduite du seul provider, protocole ou nom de filtre.
### Source Standard Logs productive ### Source Standard Logs productive
@@ -331,6 +331,12 @@ let backpressure = snapshot.backpressure_wait_total();
let reconnects = snapshot.source_reconnect_total(); let reconnects = snapshot.source_reconnect_total();
let replay_attempts = snapshot.source_replay_attempt_total(); let replay_attempts = snapshot.source_replay_attempt_total();
let proven_gaps = snapshot.source_continuity_gap_total(); let proven_gaps = snapshot.source_continuity_gap_total();
let gaps = snapshot.gaps();
let open_gaps = snapshot.open_gap_count();
let repairing_gaps = snapshot.repairing_gap_count();
let repaired_gaps = snapshot.repaired_gap_total();
let unresolved_gaps = snapshot.unresolved_gap_total();
let oldest_gap = snapshot.oldest_open_gap_start_slot();
``` ```
`admission_queue_depth()`, `in_flight_persistence()`, `hydration_pending()`, `source_total()`, `source_active()`, `source_reconnecting()` et `source_failed()` sont des gauges latest-value. Les compteurs cumulés et les agrégats multi-source ne wrapent ni ne saturent silencieusement ; l'épuisement est terminal avec `worker_raw_transaction_ingest.counter_exhausted`. `admission_queue_depth()`, `in_flight_persistence()`, `hydration_pending()`, `source_total()`, `source_active()`, `source_reconnecting()` et `source_failed()` sont des gauges latest-value. Les compteurs cumulés et les agrégats multi-source ne wrapent ni ne saturent silencieusement ; l'épuisement est terminal avec `worker_raw_transaction_ingest.counter_exhausted`.
@@ -354,7 +360,7 @@ Un `Missing` HTTP règle le signal du point de vue source-processing sans créer
`source_state()` peut retourner `Active`, `Reconnecting`, `Closing`, `Closed` ou `Failed` comme état agrégé source-neutral. Les gauges `source_total()`, `source_active()`, `source_reconnecting()` et `source_failed()` permettent d'interpréter une composition multi-source sans exposer provider, endpoint, filtre ou `source_key`. `source_state()` peut retourner `Active`, `Reconnecting`, `Closing`, `Closed` ou `Failed` comme état agrégé source-neutral. Les gauges `source_total()`, `source_active()`, `source_reconnecting()` et `source_failed()` permettent d'interpréter une composition multi-source sans exposer provider, endpoint, filtre ou `source_key`.
La health commune est conservative : pendant `Running`, au moins une source `Failed` donne `Unhealthy`; au moins une source `Reconnecting`, ou une composition dont toutes les sources attendues ne sont pas encore `Active`, donne `Degraded`; toutes les sources attendues `Active` permettent `Healthy`. Une transition Worker `Faulted` reste `Unhealthy`. Lorsque la policy de continuité est active, la health commune est plus stricte qu'une simple lecture des états source. Pendant `Running`, reconnect en cours, gap ouvert, continuity frontier différente de la processing frontier ou `TargetCoverage` futur non couvert donnent `Unhealthy`. Toutes les sources attendues `Active` avec continuité réconciliée permettent `Healthy`. Une source `Failed` ne donne `Degraded` que si sa perte est entièrement réconciliée et si les sources restantes couvrent encore tout le `TargetCoverage` futur. Une transition Worker `Faulted` reste `Unhealthy`.
Les compteurs ont des sémantiques distinctes : Les compteurs ont des sémantiques distinctes :
@@ -366,7 +372,9 @@ source_continuity_gap_total gaps de rétention prouvés par Transport
Une tentative de replay n'est pas une preuve de continuité. Le Worker ne choisit pas `from_slot` et ne traite pas directement `SubscribeReplayInfo` ; ces mécanismes appartiennent à Transport. Une tentative de replay n'est pas une preuve de continuité. Le Worker ne choisit pas `from_slot` et ne traite pas directement `SubscribeReplayInfo` ; ces mécanismes appartiennent à Transport.
Si `source_continuity_gap_total` augmente, le Worker fault avec `worker_raw_transaction_ingest.source_failed`. Il ne déclenche pas automatiquement `ksp-job-backfill-lib`. Un gap est projeté via `gaps()` avec un état `Pending`, `Repairing`, `Repaired` ou `Unresolved`, une raison source-neutral et éventuellement la dernière méthode de réparation. Les mécanismes publics décrits par `RawTransactionIngestRepairMethod` sont `Replay`, `RedundantCoverage`, `HttpScan`, `BlockFetch` et `TransactionHydration`.
Une perte source ou un gap de rétention ne déclenche jamais `ksp-job-backfill-lib`. Le Worker continue seulement lorsqu'il peut prouver la réconciliation passée et la coverage future dans les bornes du run courant ; sinon il fault.
## Demander un stop et attendre le terminal ## Demander un stop et attendre le terminal

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs // file: crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
// version: 26 // version: 33
#![warn(missing_docs)] #![warn(missing_docs)]
#![deny(unreachable_pub)] #![deny(unreachable_pub)]
@@ -18,9 +18,11 @@
//! Transaction/TransactionStatus/Block and standard logs notifications become signature/slot references; BlockMeta/Slot remain continuity-only signals. //! Transaction/TransactionStatus/Block and standard logs notifications become signature/slot references; BlockMeta/Slot remain continuity-only signals.
//! Hydration is coalesced by network/signature/commitment under bounded in-flight and pending budgets. A bounded run-local processing frontier projects //! Hydration is coalesced by network/signature/commitment under bounded in-flight and pending budgets. A bounded run-local processing frontier projects
//! hydration pending, oldest pending slot and highest unblocked actually observed slot. The productive source also projects safe Transport reconnect/replay //! hydration pending, oldest pending slot and highest unblocked actually observed slot. The productive source also projects safe Transport reconnect/replay
//! state and faults conservatively when Transport proves a replay-retention continuity gap; history remediation remains outside this crate. //! state. Run-local continuity contracts reconcile bounded source-loss and known-reference gaps only from explicit coverage epochs; a lost source may remain
//! absent only while currently active siblings preserve the configured TargetCoverage. Transport source respawn remains forbidden.
mod admission; mod admission;
mod continuity;
mod error; mod error;
mod identity; mod identity;
mod persistence; mod persistence;
@@ -97,6 +99,16 @@ pub use self::settings::MIN_RAW_TRANSACTION_INGEST_PERSISTENCE_CONCURRENCY;
pub use self::settings::MIN_RAW_TRANSACTION_INGEST_SHUTDOWN_DRAIN_TIMEOUT; pub use self::settings::MIN_RAW_TRANSACTION_INGEST_SHUTDOWN_DRAIN_TIMEOUT;
/// Validated source-neutral runtime settings for one continuous RAW transaction ingest Worker. /// Validated source-neutral runtime settings for one continuous RAW transaction ingest Worker.
pub use self::settings::RawTransactionIngestSettings; pub use self::settings::RawTransactionIngestSettings;
/// Stable source-neutral identifier of one run-local continuity gap.
pub use self::snapshot::RawTransactionIngestGapId;
/// Source-neutral reason why one run-local continuity gap was opened.
pub use self::snapshot::RawTransactionIngestGapReason;
/// Safe source-neutral snapshot of one recent or still-open run-local continuity gap.
pub use self::snapshot::RawTransactionIngestGapSnapshot;
/// Source-neutral lifecycle state of one run-local continuity gap.
pub use self::snapshot::RawTransactionIngestGapState;
/// Source-neutral mechanism last used while attempting to close one continuity gap.
pub use self::snapshot::RawTransactionIngestRepairMethod;
/// Complete safe latest-value snapshot of one continuous RAW transaction ingest Worker. /// Complete safe latest-value snapshot of one continuous RAW transaction ingest Worker.
pub use self::snapshot::RawTransactionIngestSnapshot; pub use self::snapshot::RawTransactionIngestSnapshot;
/// Runtime-neutral boxed future resolving to one newer concrete RAW transaction ingest Worker snapshot. /// Runtime-neutral boxed future resolving to one newer concrete RAW transaction ingest Worker snapshot.
@@ -110,6 +122,20 @@ pub use self::snapshot::RawTransactionIngestSourceState;
pub(crate) use self::admission::RawTransactionAdmission; pub(crate) use self::admission::RawTransactionAdmission;
/// Crate-private source-neutral ingress sent through the bounded central admission queue. /// Crate-private source-neutral ingress sent through the bounded central admission queue.
pub(crate) use self::admission::RawTransactionIngress; pub(crate) use self::admission::RawTransactionIngress;
/// Maximum number of slots admitted by one private continuity HTTP discovery window.
pub(crate) use self::continuity::MAX_RAW_TRANSACTION_INGEST_CONTINUITY_DISCOVERY_WINDOW_SLOTS;
/// Private source continuity-capability descriptor prepared without network or Store I/O.
pub(crate) use self::continuity::RawTransactionIngestContinuityCapabilityDescriptor;
/// Private run-local continuity aggregate containing capabilities, TargetCoverage and the gap ledger.
pub(crate) use self::continuity::RawTransactionIngestContinuityContracts;
/// Private provider-neutral coverage scope used by run-local continuity proof contracts.
pub(crate) use self::continuity::RawTransactionIngestCoverageScope;
/// Private run-local obligation for one transaction reference already observed by a live source.
pub(crate) use self::continuity::RawTransactionIngestKnownReferenceObligation;
/// Private supervisor decision after one live source becomes terminal.
pub(crate) use self::continuity::RawTransactionIngestSourceLossDecision;
/// Private run-local WebSocket incident anchor built only from observed source slots and safe Transport counters.
pub(crate) use self::continuity::RawTransactionIngestWebSocketIncidentAnchor;
/// Creates one terminal content-conflict error without copying conflicting material into diagnostics. /// Creates one terminal content-conflict error without copying conflicting material into diagnostics.
pub(crate) use self::error::content_conflict_error; pub(crate) use self::error::content_conflict_error;
/// Creates one terminal counter-exhaustion error without exposing runtime material. /// Creates one terminal counter-exhaustion error without exposing runtime material.
@@ -134,6 +160,8 @@ pub(crate) use self::persistence::RawTransactionIngestPersistencePort;
pub(crate) use self::persistence::persist_raw_transaction_ingest_acquisition; pub(crate) use self::persistence::persist_raw_transaction_ingest_acquisition;
/// Persists one canonical acquisition through the bounded cross-source convergence cache. /// Persists one canonical acquisition through the bounded cross-source convergence cache.
pub(crate) use self::persistence::persist_raw_transaction_ingest_converged_acquisition; pub(crate) use self::persistence::persist_raw_transaction_ingest_converged_acquisition;
/// Private bounded continuity-observability projection carried into the concrete Worker snapshot.
pub(crate) use self::snapshot::RawTransactionIngestContinuitySnapshotProjection;
/// Private latest-value processing-frontier projection emitted by the productive source task. /// Private latest-value processing-frontier projection emitted by the productive source task.
pub(crate) use self::snapshot::RawTransactionIngestProcessingFrontierProjection; pub(crate) use self::snapshot::RawTransactionIngestProcessingFrontierProjection;
/// Private latest-value publisher and checked counter owner shared by the Worker supervisor. /// Private latest-value publisher and checked counter owner shared by the Worker supervisor.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/src/runtime.rs // file: crates/ksp-worker-raw-transaction-ingest-lib/src/runtime.rs
// version: 17 // version: 18
type PersistencePort = std::sync::Arc<dyn crate::RawTransactionIngestPersistencePort + 'static>; type PersistencePort = std::sync::Arc<dyn crate::RawTransactionIngestPersistencePort + 'static>;
type PersistenceTasks = tokio::task::JoinSet<ksp_core_lib::Result<crate::RawTransactionIngestPersistenceOutcome>>; type PersistenceTasks = tokio::task::JoinSet<ksp_core_lib::Result<crate::RawTransactionIngestPersistenceOutcome>>;
@@ -647,7 +647,7 @@ async fn wait_processing_frontier(
if receiver.changed().await.is_err() { if receiver.changed().await.is_err() {
return std::option::Option::None; return std::option::Option::None;
} }
return std::option::Option::Some(*receiver.borrow_and_update()); return std::option::Option::Some(receiver.borrow_and_update().clone());
} }
fn validate_store_network(settings: &crate::RawTransactionIngestSettings, store_network: &ksp_store_lib::RawNetworkId) -> ksp_core_lib::Result<()> { fn validate_store_network(settings: &crate::RawTransactionIngestSettings, store_network: &ksp_store_lib::RawNetworkId) -> ksp_core_lib::Result<()> {

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/src/snapshot.rs // file: crates/ksp-worker-raw-transaction-ingest-lib/src/snapshot.rs
// version: 5 // version: 9
/// Runtime-neutral boxed future resolving to one newer concrete RAW transaction ingest Worker snapshot. /// Runtime-neutral boxed future resolving to one newer concrete RAW transaction ingest Worker snapshot.
pub type RawTransactionIngestSnapshotFuture<'a> = pub type RawTransactionIngestSnapshotFuture<'a> =
@@ -20,9 +20,267 @@ pub enum RawTransactionIngestSourceState {
Failed, Failed,
} }
/// Private latest-value source-processing projection emitted by the productive source task. /// Stable source-neutral identifier of one run-local continuity gap.
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct RawTransactionIngestGapId(u64);
impl crate::RawTransactionIngestGapId {
/// Creates one internal gap identifier after the continuity ledger validated monotonicity.
pub(crate) const fn new(value: u64) -> Self {
return Self(value);
}
/// Returns the run-local numeric identifier.
#[must_use]
pub const fn value(self) -> u64 {
return self.0;
}
}
/// Source-neutral lifecycle state of one run-local continuity gap.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum RawTransactionIngestGapState {
/// The gap is known and waiting for an admissible recovery proof.
Pending,
/// One bounded recovery mechanism is actively processing the gap.
Repairing,
/// The complete required interval has been proven covered.
Repaired,
/// The gap remains unresolved after the admissible bounded mechanisms are exhausted.
Unresolved,
}
/// Source-neutral reason why one run-local continuity gap was opened.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum RawTransactionIngestGapReason {
/// A slot was proven produced but its block material remained unavailable.
HttpProducedBlockUnavailable,
/// A previously observed transaction reference could not yet be materialized.
KnownReferenceMissing,
/// A logical live source became terminal before continuity was proven.
SourceFailure,
/// Transport reported bounded notification loss or overflow.
TransportOverflow,
/// A WebSocket reconnect opened a bounded continuity incident.
WebSocketReconnect,
/// Yellowstone replay retention could not cover the requested boundary.
YellowstoneRetention,
}
/// Source-neutral mechanism last used while attempting to close one continuity gap.
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum RawTransactionIngestRepairMethod {
/// Transport-owned replay supplied material later proven sufficient.
Replay,
/// A distinct live source supplied explicit interval coverage.
RedundantCoverage,
/// A bounded HTTP slot/block discovery scan supplied coverage evidence.
HttpScan,
/// A produced slot required direct block material retrieval.
BlockFetch,
/// A known transaction reference required observed transaction hydration.
TransactionHydration,
}
/// Safe source-neutral snapshot of one recent or still-open run-local continuity gap.
#[derive(Clone, Copy, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct RawTransactionIngestGapSnapshot {
gap_id: crate::RawTransactionIngestGapId,
start_slot: u64,
end_slot: u64,
state: crate::RawTransactionIngestGapState,
reason: crate::RawTransactionIngestGapReason,
last_method: std::option::Option<crate::RawTransactionIngestRepairMethod>,
}
impl crate::RawTransactionIngestGapSnapshot {
/// Creates one safe gap projection from already validated continuity-ledger state.
pub(crate) const fn new(
gap_id: crate::RawTransactionIngestGapId,
start_slot: u64,
end_slot: u64,
state: crate::RawTransactionIngestGapState,
reason: crate::RawTransactionIngestGapReason,
last_method: std::option::Option<crate::RawTransactionIngestRepairMethod>,
) -> Self {
return Self { gap_id, start_slot, end_slot, state, reason, last_method };
}
/// Returns the run-local gap identifier.
#[must_use]
pub const fn gap_id(&self) -> crate::RawTransactionIngestGapId {
return self.gap_id;
}
/// Returns the inclusive first slot of the gap.
#[must_use]
pub const fn start_slot(&self) -> u64 {
return self.start_slot;
}
/// Returns the inclusive last slot of the gap.
#[must_use]
pub const fn end_slot(&self) -> u64 {
return self.end_slot;
}
/// Returns the source-neutral gap lifecycle state.
#[must_use]
pub const fn state(&self) -> crate::RawTransactionIngestGapState {
return self.state;
}
/// Returns the source-neutral reason that opened the gap.
#[must_use]
pub const fn reason(&self) -> crate::RawTransactionIngestGapReason {
return self.reason;
}
/// Returns the last bounded recovery mechanism recorded for this gap, when any.
#[must_use]
pub const fn last_method(&self) -> std::option::Option<crate::RawTransactionIngestRepairMethod> {
return self.last_method;
}
}
/// Private bounded continuity-observability projection carried into the public latest-value Worker snapshot.
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct RawTransactionIngestContinuitySnapshotProjection {
gaps: std::vec::Vec<crate::RawTransactionIngestGapSnapshot>,
open_gap_count: usize,
repairing_gap_count: usize,
repaired_gap_total: u64,
unresolved_gap_total: u64,
replay_repair_total: u64,
redundant_coverage_repair_total: u64,
http_scan_repair_total: u64,
repair_block_fetch_total: u64,
repair_transaction_hydration_total: u64,
oldest_open_gap_start_slot: std::option::Option<u64>,
}
impl crate::RawTransactionIngestContinuitySnapshotProjection {
/// Returns the empty continuity-observability projection before any gap exists.
pub(crate) fn empty() -> Self {
return Self {
gaps: std::vec::Vec::new(),
open_gap_count: 0,
repairing_gap_count: 0,
repaired_gap_total: 0,
unresolved_gap_total: 0,
replay_repair_total: 0,
redundant_coverage_repair_total: 0,
http_scan_repair_total: 0,
repair_block_fetch_total: 0,
repair_transaction_hydration_total: 0,
oldest_open_gap_start_slot: std::option::Option::None,
};
}
/// Returns a copy carrying bounded gap details and current open-gap aggregates.
pub(crate) fn with_gap_state(
mut self,
gaps: std::vec::Vec<crate::RawTransactionIngestGapSnapshot>,
open_gap_count: usize,
repairing_gap_count: usize,
oldest_open_gap_start_slot: std::option::Option<u64>,
) -> Self {
self.gaps = gaps;
self.open_gap_count = open_gap_count;
self.repairing_gap_count = repairing_gap_count;
self.oldest_open_gap_start_slot = oldest_open_gap_start_slot;
return self;
}
/// Returns a copy carrying cumulative source-neutral gap recovery totals.
pub(crate) fn with_recovery_totals(
mut self,
repaired_gap_total: u64,
unresolved_gap_total: u64,
replay_repair_total: u64,
redundant_coverage_repair_total: u64,
http_scan_repair_total: u64,
) -> Self {
self.repaired_gap_total = repaired_gap_total;
self.unresolved_gap_total = unresolved_gap_total;
self.replay_repair_total = replay_repair_total;
self.redundant_coverage_repair_total = redundant_coverage_repair_total;
self.http_scan_repair_total = http_scan_repair_total;
return self;
}
/// Returns a copy carrying cumulative bounded material-recovery totals.
pub(crate) fn with_material_totals(mut self, repair_block_fetch_total: u64, repair_transaction_hydration_total: u64) -> Self {
self.repair_block_fetch_total = repair_block_fetch_total;
self.repair_transaction_hydration_total = repair_transaction_hydration_total;
return self;
}
/// Returns the bounded recent/open gap projections.
pub(crate) fn gaps(&self) -> &[crate::RawTransactionIngestGapSnapshot] {
return self.gaps.as_slice();
}
/// Returns the current number of open gaps.
pub(crate) const fn open_gap_count(&self) -> usize {
return self.open_gap_count;
}
/// Returns the current number of actively recovering gaps.
pub(crate) const fn repairing_gap_count(&self) -> usize {
return self.repairing_gap_count;
}
/// Returns the cumulative repaired-gap total.
pub(crate) const fn repaired_gap_total(&self) -> u64 {
return self.repaired_gap_total;
}
/// Returns the cumulative unresolved-gap total.
pub(crate) const fn unresolved_gap_total(&self) -> u64 {
return self.unresolved_gap_total;
}
/// Returns the cumulative replay recovery total.
pub(crate) const fn replay_repair_total(&self) -> u64 {
return self.replay_repair_total;
}
/// Returns the cumulative distinct-source coverage recovery total.
pub(crate) const fn redundant_coverage_repair_total(&self) -> u64 {
return self.redundant_coverage_repair_total;
}
/// Returns the cumulative bounded HTTP scan recovery total.
pub(crate) const fn http_scan_repair_total(&self) -> u64 {
return self.http_scan_repair_total;
}
/// Returns the cumulative direct block material recovery total.
pub(crate) const fn repair_block_fetch_total(&self) -> u64 {
return self.repair_block_fetch_total;
}
/// Returns the cumulative transaction hydration recovery total.
pub(crate) const fn repair_transaction_hydration_total(&self) -> u64 {
return self.repair_transaction_hydration_total;
}
/// Returns the oldest first slot among currently open gaps.
pub(crate) const fn oldest_open_gap_start_slot(&self) -> std::option::Option<u64> {
return self.oldest_open_gap_start_slot;
}
}
/// Private latest-value source-processing projection emitted by the productive source task.
#[derive(Clone, Debug, Eq, PartialEq)]
pub(crate) struct RawTransactionIngestProcessingFrontierProjection { pub(crate) struct RawTransactionIngestProcessingFrontierProjection {
continuity_frontier_slot: std::option::Option<u64>,
continuity_has_open_gaps: bool,
continuity_policy_observed: bool,
failed_source_losses_reconciled: bool,
future_target_coverage: bool,
continuity_snapshot: crate::RawTransactionIngestContinuitySnapshotProjection,
hydration_pending: usize, hydration_pending: usize,
processing_frontier_slot: std::option::Option<u64>, processing_frontier_slot: std::option::Option<u64>,
oldest_pending_slot: std::option::Option<u64>, oldest_pending_slot: std::option::Option<u64>,
@@ -38,8 +296,14 @@ pub(crate) struct RawTransactionIngestProcessingFrontierProjection {
impl crate::RawTransactionIngestProcessingFrontierProjection { impl crate::RawTransactionIngestProcessingFrontierProjection {
/// Returns the empty run-local processing projection used before the source observes work. /// Returns the empty run-local processing projection used before the source observes work.
pub(crate) const fn empty() -> Self { pub(crate) fn empty() -> Self {
return Self { return Self {
continuity_frontier_slot: std::option::Option::None,
continuity_has_open_gaps: false,
continuity_policy_observed: false,
failed_source_losses_reconciled: false,
future_target_coverage: false,
continuity_snapshot: crate::RawTransactionIngestContinuitySnapshotProjection::empty(),
hydration_pending: 0, hydration_pending: 0,
processing_frontier_slot: std::option::Option::None, processing_frontier_slot: std::option::Option::None,
oldest_pending_slot: std::option::Option::None, oldest_pending_slot: std::option::Option::None,
@@ -55,12 +319,14 @@ impl crate::RawTransactionIngestProcessingFrontierProjection {
} }
/// Creates one run-local processing projection from bounded source-owned state. /// Creates one run-local processing projection from bounded source-owned state.
pub(crate) const fn new( pub(crate) fn new(hydration_pending: usize, processing_frontier_slot: std::option::Option<u64>, oldest_pending_slot: std::option::Option<u64>) -> Self {
hydration_pending: usize,
processing_frontier_slot: std::option::Option<u64>,
oldest_pending_slot: std::option::Option<u64>,
) -> Self {
return Self { return Self {
continuity_frontier_slot: std::option::Option::None,
continuity_has_open_gaps: false,
continuity_policy_observed: false,
failed_source_losses_reconciled: false,
future_target_coverage: false,
continuity_snapshot: crate::RawTransactionIngestContinuitySnapshotProjection::empty(),
hydration_pending, hydration_pending,
processing_frontier_slot, processing_frontier_slot,
oldest_pending_slot, oldest_pending_slot,
@@ -91,7 +357,7 @@ impl crate::RawTransactionIngestProcessingFrontierProjection {
} }
/// Returns a copy carrying the latest source reconnect/replay projection. /// Returns a copy carrying the latest source reconnect/replay projection.
pub(crate) const fn with_source_continuity( pub(crate) fn with_source_continuity(
mut self, mut self,
source_state: std::option::Option<crate::RawTransactionIngestSourceState>, source_state: std::option::Option<crate::RawTransactionIngestSourceState>,
source_reconnect_total: u64, source_reconnect_total: u64,
@@ -106,7 +372,7 @@ impl crate::RawTransactionIngestProcessingFrontierProjection {
} }
/// Returns a copy carrying source-neutral multi-source lifecycle counts. /// Returns a copy carrying source-neutral multi-source lifecycle counts.
pub(crate) const fn with_source_counts(mut self, source_total: usize, source_active: usize, source_reconnecting: usize, source_failed: usize) -> Self { pub(crate) fn with_source_counts(mut self, source_total: usize, source_active: usize, source_reconnecting: usize, source_failed: usize) -> Self {
self.source_total = source_total; self.source_total = source_total;
self.source_active = source_active; self.source_active = source_active;
self.source_reconnecting = source_reconnecting; self.source_reconnecting = source_reconnecting;
@@ -114,6 +380,62 @@ impl crate::RawTransactionIngestProcessingFrontierProjection {
return self; return self;
} }
/// Returns a copy carrying source-neutral run-local continuity evidence used only for Worker health classification.
pub(crate) fn with_continuity_health(
mut self,
continuity_frontier_slot: std::option::Option<u64>,
continuity_has_open_gaps: bool,
future_target_coverage: bool,
) -> Self {
self.continuity_frontier_slot = continuity_frontier_slot;
self.continuity_has_open_gaps = continuity_has_open_gaps;
self.continuity_policy_observed = true;
self.future_target_coverage = future_target_coverage;
return self;
}
/// Returns a copy carrying bounded source-neutral continuity observability.
pub(crate) fn with_continuity_snapshot(mut self, continuity_snapshot: crate::RawTransactionIngestContinuitySnapshotProjection) -> Self {
self.continuity_snapshot = continuity_snapshot;
return self;
}
/// Returns the bounded source-neutral continuity observability carried by this projection.
pub(crate) const fn continuity_snapshot(&self) -> &crate::RawTransactionIngestContinuitySnapshotProjection {
return &self.continuity_snapshot;
}
/// Returns whether a run-local continuity policy projection has been observed.
pub(crate) const fn continuity_policy_observed(&self) -> bool {
return self.continuity_policy_observed;
}
/// Returns the gap-aware continuity frontier carried by this private projection.
pub(crate) const fn continuity_frontier_slot(&self) -> std::option::Option<u64> {
return self.continuity_frontier_slot;
}
/// Returns whether at least one run-local continuity gap remains unresolved.
pub(crate) const fn continuity_has_open_gaps(&self) -> bool {
return self.continuity_has_open_gaps;
}
/// Returns whether currently Active sources still cover the complete configured future `TargetCoverage`.
pub(crate) const fn future_target_coverage(&self) -> bool {
return self.future_target_coverage;
}
/// Returns a copy carrying whether every terminal Failed source has a reconciled source-loss gap.
pub(crate) fn with_failed_source_losses_reconciled(mut self, failed_source_losses_reconciled: bool) -> Self {
self.failed_source_losses_reconciled = failed_source_losses_reconciled;
return self;
}
/// Returns whether every terminal Failed source has a reconciled source-loss gap.
pub(crate) const fn failed_source_losses_reconciled(&self) -> bool {
return self.failed_source_losses_reconciled;
}
/// Returns the configured logical source count carried by this private projection. /// Returns the configured logical source count carried by this private projection.
pub(crate) const fn source_total(&self) -> usize { pub(crate) const fn source_total(&self) -> usize {
return self.source_total; return self.source_total;
@@ -159,6 +481,22 @@ impl crate::RawTransactionIngestProcessingFrontierProjection {
#[derive(Clone, Eq, PartialEq)] #[derive(Clone, Eq, PartialEq)]
pub struct RawTransactionIngestSnapshot { pub struct RawTransactionIngestSnapshot {
worker: ksp_worker_api::WorkerSnapshot, worker: ksp_worker_api::WorkerSnapshot,
continuity_frontier_slot: std::option::Option<u64>,
continuity_has_open_gaps: bool,
continuity_policy_observed: bool,
failed_source_losses_reconciled: bool,
future_target_coverage: bool,
gaps: std::vec::Vec<crate::RawTransactionIngestGapSnapshot>,
open_gap_count: usize,
repairing_gap_count: usize,
repaired_gap_total: u64,
unresolved_gap_total: u64,
replay_repair_total: u64,
redundant_coverage_repair_total: u64,
http_scan_repair_total: u64,
repair_block_fetch_total: u64,
repair_transaction_hydration_total: u64,
oldest_open_gap_start_slot: std::option::Option<u64>,
admission_queue_capacity: usize, admission_queue_capacity: usize,
admission_queue_depth: usize, admission_queue_depth: usize,
persistence_concurrency: usize, persistence_concurrency: usize,
@@ -309,6 +647,74 @@ impl crate::RawTransactionIngestSnapshot {
return self.oldest_pending_slot; return self.oldest_pending_slot;
} }
/// Returns the bounded source-neutral gap projections retained in this latest-value snapshot.
///
/// Every open gap is retained while capacity remains bounded; recent repaired entries may fill unused projection capacity.
#[must_use]
pub fn gaps(&self) -> &[crate::RawTransactionIngestGapSnapshot] {
return self.gaps.as_slice();
}
/// Returns the number of currently open run-local continuity gaps.
#[must_use]
pub const fn open_gap_count(&self) -> usize {
return self.open_gap_count;
}
/// Returns the number of currently active gap recoveries.
#[must_use]
pub const fn repairing_gap_count(&self) -> usize {
return self.repairing_gap_count;
}
/// Returns the number of gaps cumulatively proven repaired during this run.
#[must_use]
pub const fn repaired_gap_total(&self) -> u64 {
return self.repaired_gap_total;
}
/// Returns the number of gaps cumulatively classified unresolved during this run.
#[must_use]
pub const fn unresolved_gap_total(&self) -> u64 {
return self.unresolved_gap_total;
}
/// Returns the number of gaps whose latest successful recovery proof used replay.
#[must_use]
pub const fn replay_repair_total(&self) -> u64 {
return self.replay_repair_total;
}
/// Returns the number of gaps whose latest successful recovery proof used distinct-source coverage.
#[must_use]
pub const fn redundant_coverage_repair_total(&self) -> u64 {
return self.redundant_coverage_repair_total;
}
/// Returns the number of gaps whose latest successful recovery proof used a bounded HTTP scan.
#[must_use]
pub const fn http_scan_repair_total(&self) -> u64 {
return self.http_scan_repair_total;
}
/// Returns the number of repaired gaps whose last recorded mechanism was direct block material retrieval.
#[must_use]
pub const fn repair_block_fetch_total(&self) -> u64 {
return self.repair_block_fetch_total;
}
/// Returns the number of repaired gaps whose last recorded mechanism was transaction hydration.
#[must_use]
pub const fn repair_transaction_hydration_total(&self) -> u64 {
return self.repair_transaction_hydration_total;
}
/// Returns the oldest first slot among currently open continuity gaps.
#[must_use]
pub const fn oldest_open_gap_start_slot(&self) -> std::option::Option<u64> {
return self.oldest_open_gap_start_slot;
}
/// Returns the configured number of logical live sources for this Worker run. /// Returns the configured number of logical live sources for this Worker run.
#[must_use] #[must_use]
pub const fn source_total(&self) -> usize { pub const fn source_total(&self) -> usize {
@@ -363,6 +769,17 @@ impl std::fmt::Debug for crate::RawTransactionIngestSnapshot {
return formatter return formatter
.debug_struct("RawTransactionIngestSnapshot") .debug_struct("RawTransactionIngestSnapshot")
.field("worker", &self.worker) .field("worker", &self.worker)
.field("gaps", &self.gaps)
.field("open_gap_count", &self.open_gap_count)
.field("repairing_gap_count", &self.repairing_gap_count)
.field("repaired_gap_total", &self.repaired_gap_total)
.field("unresolved_gap_total", &self.unresolved_gap_total)
.field("replay_repair_total", &self.replay_repair_total)
.field("redundant_coverage_repair_total", &self.redundant_coverage_repair_total)
.field("http_scan_repair_total", &self.http_scan_repair_total)
.field("repair_block_fetch_total", &self.repair_block_fetch_total)
.field("repair_transaction_hydration_total", &self.repair_transaction_hydration_total)
.field("oldest_open_gap_start_slot", &self.oldest_open_gap_start_slot)
.field("admission_queue_capacity", &self.admission_queue_capacity) .field("admission_queue_capacity", &self.admission_queue_capacity)
.field("admission_queue_depth", &self.admission_queue_depth) .field("admission_queue_depth", &self.admission_queue_depth)
.field("persistence_concurrency", &self.persistence_concurrency) .field("persistence_concurrency", &self.persistence_concurrency)
@@ -488,6 +905,22 @@ impl crate::RawTransactionIngestSnapshotPublisher {
); );
let snapshot = crate::RawTransactionIngestSnapshot { let snapshot = crate::RawTransactionIngestSnapshot {
worker, worker,
continuity_frontier_slot: std::option::Option::None,
continuity_has_open_gaps: false,
continuity_policy_observed: false,
failed_source_losses_reconciled: false,
future_target_coverage: false,
gaps: std::vec::Vec::new(),
open_gap_count: 0,
repairing_gap_count: 0,
repaired_gap_total: 0,
unresolved_gap_total: 0,
replay_repair_total: 0,
redundant_coverage_repair_total: 0,
http_scan_repair_total: 0,
repair_block_fetch_total: 0,
repair_transaction_hydration_total: 0,
oldest_open_gap_start_slot: std::option::Option::None,
admission_queue_capacity: settings.admission_queue_capacity(), admission_queue_capacity: settings.admission_queue_capacity(),
admission_queue_depth: 0, admission_queue_depth: 0,
persistence_concurrency: settings.persistence_concurrency(), persistence_concurrency: settings.persistence_concurrency(),
@@ -527,14 +960,7 @@ impl crate::RawTransactionIngestSnapshotPublisher {
self.snapshot.worker.kind().clone(), self.snapshot.worker.kind().clone(),
self.snapshot.worker.sequence(), self.snapshot.worker.sequence(),
state, state,
health_for_state( health_for_state(state, self.snapshot.worker.health(), &self.snapshot),
state,
self.snapshot.worker.health(),
self.snapshot.source_total,
self.snapshot.source_active,
self.snapshot.source_reconnecting,
self.snapshot.source_failed,
),
ksp_worker_api::WorkerActivity::Idle, ksp_worker_api::WorkerActivity::Idle,
); );
self.snapshot.worker = worker; self.snapshot.worker = worker;
@@ -611,6 +1037,23 @@ impl crate::RawTransactionIngestSnapshotPublisher {
in_flight_persistence: usize, in_flight_persistence: usize,
projection: crate::RawTransactionIngestProcessingFrontierProjection, projection: crate::RawTransactionIngestProcessingFrontierProjection,
) -> ksp_core_lib::Result<()> { ) -> ksp_core_lib::Result<()> {
self.snapshot.continuity_frontier_slot = projection.continuity_frontier_slot();
self.snapshot.continuity_has_open_gaps = projection.continuity_has_open_gaps();
self.snapshot.continuity_policy_observed = projection.continuity_policy_observed();
self.snapshot.failed_source_losses_reconciled = projection.failed_source_losses_reconciled();
self.snapshot.future_target_coverage = projection.future_target_coverage();
let continuity_snapshot = projection.continuity_snapshot();
self.snapshot.gaps = continuity_snapshot.gaps().to_vec();
self.snapshot.open_gap_count = continuity_snapshot.open_gap_count();
self.snapshot.repairing_gap_count = continuity_snapshot.repairing_gap_count();
self.snapshot.repaired_gap_total = continuity_snapshot.repaired_gap_total();
self.snapshot.unresolved_gap_total = continuity_snapshot.unresolved_gap_total();
self.snapshot.replay_repair_total = continuity_snapshot.replay_repair_total();
self.snapshot.redundant_coverage_repair_total = continuity_snapshot.redundant_coverage_repair_total();
self.snapshot.http_scan_repair_total = continuity_snapshot.http_scan_repair_total();
self.snapshot.repair_block_fetch_total = continuity_snapshot.repair_block_fetch_total();
self.snapshot.repair_transaction_hydration_total = continuity_snapshot.repair_transaction_hydration_total();
self.snapshot.oldest_open_gap_start_slot = continuity_snapshot.oldest_open_gap_start_slot();
self.snapshot.hydration_pending = projection.hydration_pending(); self.snapshot.hydration_pending = projection.hydration_pending();
self.snapshot.processing_frontier_slot = projection.processing_frontier_slot(); self.snapshot.processing_frontier_slot = projection.processing_frontier_slot();
self.snapshot.oldest_pending_slot = projection.oldest_pending_slot(); self.snapshot.oldest_pending_slot = projection.oldest_pending_slot();
@@ -737,14 +1180,7 @@ impl crate::RawTransactionIngestSnapshotPublisher {
self.snapshot.worker.kind().clone(), self.snapshot.worker.kind().clone(),
sequence, sequence,
state, state,
health_for_state( health_for_state(state, self.snapshot.worker.health(), &self.snapshot),
state,
self.snapshot.worker.health(),
self.snapshot.source_total,
self.snapshot.source_active,
self.snapshot.source_reconnecting,
self.snapshot.source_failed,
),
activity_for_state(state, admission_queue_depth, in_flight_persistence, self.snapshot.hydration_pending, self.snapshot.source_state), activity_for_state(state, admission_queue_depth, in_flight_persistence, self.snapshot.hydration_pending, self.snapshot.source_state),
); );
self.snapshot.worker = worker; self.snapshot.worker = worker;
@@ -799,15 +1235,38 @@ fn checked_optional_counter(current: u64, increment: bool, field: &'static str)
fn health_for_state( fn health_for_state(
state: ksp_worker_api::WorkerState, state: ksp_worker_api::WorkerState,
previous: ksp_worker_api::WorkerHealth, previous: ksp_worker_api::WorkerHealth,
source_total: usize, snapshot: &crate::RawTransactionIngestSnapshot,
source_active: usize,
source_reconnecting: usize,
source_failed: usize,
) -> ksp_worker_api::WorkerHealth { ) -> ksp_worker_api::WorkerHealth {
if state == ksp_worker_api::WorkerState::Running && snapshot.continuity_policy_observed {
let source_total = snapshot.source_total;
let source_active = snapshot.source_active;
let source_failed = snapshot.source_failed;
if snapshot.source_reconnecting > 0
|| snapshot.continuity_has_open_gaps
|| snapshot.continuity_frontier_slot != snapshot.processing_frontier_slot
|| !snapshot.future_target_coverage
{
return ksp_worker_api::WorkerHealth::Unhealthy;
}
if source_total > 0 && snapshot.source_active == snapshot.source_total {
return ksp_worker_api::WorkerHealth::Healthy;
}
if source_total > 0
&& source_active < source_total
&& source_failed > 0
&& snapshot.failed_source_losses_reconciled
&& source_failed == source_total - source_active
{
return ksp_worker_api::WorkerHealth::Degraded;
}
return ksp_worker_api::WorkerHealth::Unhealthy;
}
return match state { return match state {
ksp_worker_api::WorkerState::Running if source_failed > 0 => ksp_worker_api::WorkerHealth::Unhealthy, ksp_worker_api::WorkerState::Running if snapshot.source_failed > 0 => ksp_worker_api::WorkerHealth::Unhealthy,
ksp_worker_api::WorkerState::Running if source_reconnecting > 0 => ksp_worker_api::WorkerHealth::Degraded, ksp_worker_api::WorkerState::Running if snapshot.source_reconnecting > 0 => ksp_worker_api::WorkerHealth::Degraded,
ksp_worker_api::WorkerState::Running if source_total > 0 && source_active < source_total => ksp_worker_api::WorkerHealth::Degraded, ksp_worker_api::WorkerState::Running if snapshot.source_total > 0 && snapshot.source_active < snapshot.source_total => {
ksp_worker_api::WorkerHealth::Degraded
},
ksp_worker_api::WorkerState::Running => ksp_worker_api::WorkerHealth::Healthy, ksp_worker_api::WorkerState::Running => ksp_worker_api::WorkerHealth::Healthy,
ksp_worker_api::WorkerState::Stopping | ksp_worker_api::WorkerState::Stopped => previous, ksp_worker_api::WorkerState::Stopping | ksp_worker_api::WorkerState::Stopped => previous,
ksp_worker_api::WorkerState::Faulted(_) => ksp_worker_api::WorkerHealth::Unhealthy, ksp_worker_api::WorkerState::Faulted(_) => ksp_worker_api::WorkerHealth::Unhealthy,

View File

@@ -1,7 +1,7 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/tests/cross_layer_completeness.rs // file: crates/ksp-worker-raw-transaction-ingest-lib/tests/cross_layer_completeness.rs
// version: 2 // version: 3
//! Cross-layer completeness and security canaries through `0.3.13-pre.012`. //! Cross-layer completeness and security canaries through `0.3.14-pre.013`.
fn dependency_names(manifest: &str) -> std::vec::Vec<&str> { fn dependency_names(manifest: &str) -> std::vec::Vec<&str> {
let mut section = ""; let mut section = "";
@@ -363,3 +363,127 @@ fn v0_3_13_pre_012_security_redaction_matrix_covers_all_live_sources_and_lower_l
assert!(postgres.contains("pre_009_backend_error_bridge_cannot_retain_external_error_or_secret_text")); assert!(postgres.contains("pre_009_backend_error_bridge_cannot_retain_external_error_or_secret_text"));
return; return;
} }
#[test]
fn v0_3_14_pre_013_gap_repair_closure_keeps_one_transport_worker_common_raw_store_pipeline() {
let transport_transactions = include_str!("../../ksp-onchain-transport-lib/src/rpc_transactions.rs");
let transport_blocks = include_str!("../../ksp-onchain-transport-lib/src/rpc_blocks.rs");
let worker_root = include_str!("../src/lib.rs");
let worker_resources = include_str!("../src/runtime_resources.rs");
let worker_runtime = include_str!("../src/runtime.rs");
let worker_persistence = include_str!("../src/persistence.rs");
let common_raw_root = include_str!("../../ksp-raw-transaction-lib/src/lib.rs");
let store_api_root = include_str!("../../ksp-store-api/src/lib.rs");
let store = include_str!("../../ksp-store-lib/src/store.rs");
assert!(transport_transactions.contains("pub async fn get_transaction_observed("));
assert!(transport_blocks.contains("pub async fn get_block_observed("));
for required in [
"RawTransactionIngestContinuityContracts",
"RawTransactionIngestGlobalHydrationRegistry",
"RawTransactionAdmission",
"persist_raw_transaction_ingest_converged_acquisition",
] {
assert!(
worker_root.contains(required) || worker_resources.contains(required) || worker_runtime.contains(required),
"pre.013 Worker single-pipeline proof missing: {required}"
);
}
assert!(worker_persistence.contains("ksp_store_lib::RawTransactionWrite::persist_raw_transaction_acquisition"));
assert!(common_raw_root.contains("pub use self::acquisition::RawTransactionAcquisition;"));
assert!(common_raw_root.contains("pub use self::wire::RawSolanaMessageVersion;"));
assert!(store_api_root.contains("pub use self::model::raw_transaction::RawTransaction;"));
assert!(store_api_root.contains("pub use self::model::raw_transaction::RawTransactionObservation;"));
assert!(store.contains("persist_raw_transaction_acquisition"));
assert!(store.contains("record_raw_transaction_observation"));
for forbidden in [
"ksp_store_postgres_lib::",
"ksp_config_lib::",
"ksp_job_backfill_lib::",
"reqwest::",
"tokio_tungstenite::connect_async",
"yellowstone_grpc_proto::",
] {
assert!(
!worker_runtime.contains(forbidden) && !worker_resources.contains(forbidden) && !worker_persistence.contains(forbidden),
"pre.013 Worker bypassed the single facade pipeline: {forbidden}"
);
}
return;
}
#[test]
fn v0_3_14_pre_013_security_redaction_covers_gap_repair_and_terminal_paths() {
let transport_subscribe = include_str!("../../ksp-onchain-transport-lib/unit_tests/grpc_subscribe.rs");
let transport_unary = include_str!("../../ksp-onchain-transport-lib/unit_tests/grpc_unary.rs");
let worker_hardening = include_str!("hardening.rs");
let worker_root = include_str!("../src/lib.rs");
let common_raw = include_str!("../../ksp-raw-transaction-lib/unit_tests/canonical.rs");
let store_api = include_str!("../../ksp-store-api/tests/security_hardening.rs");
let store = include_str!("../../ksp-store-lib/tests/hardening_completeness.rs");
let postgres = include_str!("../../ksp-store-postgres-lib/tests/hardening_completeness.rs");
assert!(transport_subscribe.contains("yellowstone_subscribe_debug_omits_filter_names_and_future_payloads"));
assert!(transport_unary.contains("yellowstone_remote_status_does_not_copy_message_details_or_metadata"));
for required in [
"v0_3_14_pre_009_health_policy_is_present_future_coverage_gated_and_source_neutral",
"v0_3_14_pre_010_repair_fairness_shares_existing_bounds_without_second_pipeline",
"v0_3_14_pre_011_gap_observability_is_bounded_checked_and_redacted",
"v0_3_14_pre_012_shutdown_races_are_bounded_across_source_and_worker_drains",
] {
assert!(worker_hardening.contains(required), "pre.013 Worker security closure proof missing: {required}");
}
for forbidden in ["source_key", "provider_gap", "endpoint_gap", "signature_gap", "payload_gap"] {
assert!(!worker_root.contains(forbidden), "pre.013 public Worker root leaked sensitive gap material: {forbidden}");
}
assert!(common_raw.contains("pre_002_material_and_wire_debug_do_not_render_transaction_or_meta_material"));
assert!(store_api.contains("pre_007_raw_debug_surfaces_do_not_render_payload_hash_signature_or_account_bytes"));
assert!(store.contains("pre_009_secret_canary_never_crosses_settings_or_pre_io_error_debug"));
assert!(postgres.contains("pre_009_backend_error_bridge_cannot_retain_external_error_or_secret_text"));
return;
}
#[test]
fn v0_3_14_pre_013_legacy_v0_v1_remains_proven_after_gap_repair_hardening() {
let transport = include_str!("../../ksp-onchain-transport-lib/unit_tests/grpc_subscribe.rs");
let worker = include_str!("../unit_tests/runtime_resources.rs");
let common_raw = include_str!("../../ksp-raw-transaction-lib/unit_tests/wire.rs");
let store_api = include_str!("../../ksp-store-api/tests/public_api.rs");
for required in [
"yellowstone_transaction_update_decodes_current_storage_wire_including_v1_config_and_meta",
"yellowstone_block_update_reuses_transaction_account_entry_dtos_and_preserves_server_counts",
] {
assert!(transport.contains(required), "pre.013 Transport Legacy/V0/V1 proof missing: {required}");
}
for required in [
"v0_3_13_pre_004_legacy_v0_v1_block_materials_are_direct_common_raw_with_exact_version_and_index",
"v0_3_13_pre_006_http_block_polling_qualifies_legacy_v0_v1_and_rejects_ambiguous_or_future_versions",
"v0_3_14_pre_008_proven_full_ledger_epoch_survives_filtered_peer_loss_without_worker_respawn",
"v0_3_14_pre_012_source_fault_preserves_first_fault_and_aborts_non_cooperative_sibling",
] {
assert!(worker.contains(required), "pre.013 Worker Legacy/V0/V1 or continuity proof missing: {required}");
}
for required in [
"pre_006_legacy_v0_and_v1_wire_goldens_are_exact",
"RawSolanaMessageVersion::Legacy",
"RawSolanaMessageVersion::V0",
"RawSolanaMessageVersion::V1",
] {
assert!(common_raw.contains(required), "pre.013 Common RAW version proof missing: {required}");
}
assert!(store_api.contains("public_pre_003_raw_transaction_and_observation_are_constructible_from_crate_root"));
return;
}
#[test]
fn v0_3_14_pre_013_worker_and_backfill_remain_independent_producers() {
let worker = dependency_names(include_str!("../Cargo.toml"));
let backfill = dependency_names(include_str!("../../ksp-job-backfill-lib/Cargo.toml"));
assert!(!worker.contains(&"ksp-job-api"));
assert!(!worker.contains(&"ksp-job-backfill-lib"));
assert!(!backfill.contains(&"ksp-worker-api"));
assert!(!backfill.contains(&"ksp-worker-raw-transaction-ingest-lib"));
for shared in ["ksp-onchain-transport-lib", "ksp-raw-transaction-lib", "ksp-store-lib"] {
assert!(worker.contains(&shared), "Worker common RAW producer edge missing: {shared}");
assert!(backfill.contains(&shared), "Backfill common RAW producer edge missing: {shared}");
}
return;
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs // file: crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs
// version: 29 // version: 31
//! Dependency firewall canaries for the RAW transaction ingest Worker foundation. //! Dependency firewall canaries for the RAW transaction ingest Worker foundation.
@@ -732,3 +732,62 @@ fn v0_3_13_pre_012_cross_layer_closure_does_not_expand_worker_dependency_graph()
} }
return; return;
} }
#[test]
fn v0_3_14_pre_012_shutdown_hardening_stays_worker_local_and_facade_only() {
let runtime = include_str!("../src/runtime.rs");
let resources = include_str!("../src/runtime_resources.rs");
for required in [
"tokio::time::timeout(settings.shutdown_drain_timeout(), drain).await",
"tokio::time::timeout(shutdown_drain_timeout, drain).await",
"children.abort_all()",
"persistence.abort_all()",
] {
assert!(runtime.contains(required) || resources.contains(required), "required pre.012 bounded shutdown guard missing: {required}");
}
for forbidden in [
"ksp_store_postgres_lib::",
"ksp_config_lib::",
"ksp_job_backfill_lib::",
"reqwest::",
"tokio_tungstenite::",
"tonic::",
"yellowstone_grpc_proto::",
] {
assert!(!runtime.contains(forbidden) && !resources.contains(forbidden), "pre.012 added forbidden Worker dependency edge: {forbidden}");
}
return;
}
#[test]
fn v0_3_14_pre_013_cross_layer_closure_keeps_dependency_and_producer_boundaries_exact() {
let manifest = include_str!("../Cargo.toml");
let dependencies = dependency_section(manifest);
let names = manifest_dependency_names(dependencies);
assert_eq!(
names,
vec![
"ksp-core-lib",
"ksp-logging-lib",
"ksp-onchain-transport-lib",
"ksp-raw-transaction-lib",
"ksp-store-lib",
"ksp-worker-api",
"sha2",
"tokio",
],
);
let cross_layer = include_str!("cross_layer_completeness.rs");
for required in [
"v0_3_14_pre_013_gap_repair_closure_keeps_one_transport_worker_common_raw_store_pipeline",
"v0_3_14_pre_013_security_redaction_covers_gap_repair_and_terminal_paths",
"v0_3_14_pre_013_legacy_v0_v1_remains_proven_after_gap_repair_hardening",
"v0_3_14_pre_013_worker_and_backfill_remain_independent_producers",
] {
assert!(cross_layer.contains(required), "pre.013 cross-layer closure proof missing: {required}");
}
for forbidden in ["ksp-config-lib", "ksp-job-api", "ksp-job-backfill-lib", "ksp-store-api", "ksp-store-postgres-lib"] {
assert!(!names.contains(&forbidden), "pre.013 Worker dependency boundary expanded: {forbidden}");
}
return;
}

View File

@@ -1,7 +1,7 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs // file: crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
// version: 24 // version: 37
//! External public, security, redaction and release-boundary hardening canaries through `pre.012`. //! External public, security, redaction and release-boundary hardening canaries through `v0.3.14-pre.011`.
fn network(value: &'static str) -> std::option::Option<ksp_store_lib::RawNetworkId> { fn network(value: &'static str) -> std::option::Option<ksp_store_lib::RawNetworkId> {
let result = ksp_store_lib::RawNetworkId::new(value); let result = ksp_store_lib::RawNetworkId::new(value);
@@ -957,3 +957,420 @@ fn v0_3_13_pre_012_cross_layer_security_closure_is_complete_and_source_neutral()
} }
return; return;
} }
#[test]
fn v0_3_14_pre_002_continuity_contracts_are_private_bounded_and_io_free() {
let continuity = include_str!("../src/continuity.rs");
let resources = include_str!("../src/runtime_resources.rs");
let root = include_str!("../src/lib.rs");
for required in [
"MAX_RAW_TRANSACTION_INGEST_OPEN_REPAIR_GAPS",
"MAX_RAW_TRANSACTION_INGEST_REPAIR_ACTIVE_GAPS",
"MAX_RAW_TRANSACTION_INGEST_REPAIR_BLOCK_FETCH_IN_FLIGHT",
"MAX_RAW_TRANSACTION_INGEST_REPAIR_DISCOVERY_WINDOW_SLOTS",
"MAX_RAW_TRANSACTION_INGEST_REPAIR_RANGE_SLOTS",
"FullLedgerTransactions",
"ExactSourceScope",
"KnownReferences",
"RawTransactionIngestGapLedger",
"RawTransactionIngestTargetCoverage",
"RawTransactionIngestContinuityCapabilityDescriptor",
"RawTransactionIngestContinuityContracts",
"continuity.known_references_not_target_scope",
"continuity.coalescible_gap_ranges",
] {
assert!(continuity.contains(required), "required pre.002 continuity contract missing: {required}");
}
for required in [
"repair_capability_descriptor",
"yellowstone_coverage_scope_fingerprint",
"http_role_supports_repair_scan",
"RawTransactionIngestContinuityContracts::new",
"validate_for_source_count",
] {
assert!(resources.contains(required), "required pre.002 runtime-resource capability wiring missing: {required}");
}
for forbidden in [
"get_block_observed",
"get_transaction_observed",
"get_blocks_with_limit",
"get_blocks(",
"open_standard_subscribe",
"SolanaStandardWsSession::connect",
"HeliusLaserStreamWsSession::connect",
"ksp_config_lib::",
"ksp_job_backfill_lib::",
"ksp_store_postgres_lib::",
"reqwest::",
"tonic::",
"yellowstone_grpc_proto::",
] {
assert!(!continuity.contains(forbidden), "pre.002 continuity contract performed or imported forbidden I/O/boundary: {forbidden}");
}
assert!(!root.contains("pub use self::continuity::"));
assert!(!root.contains("repair"), "pre.002 private continuity wiring leaked repair responsibility through crate root");
assert!(root.contains("pub(crate) use self::continuity::RawTransactionIngestContinuityContracts;"));
return;
}
#[test]
fn v0_3_14_pre_003_websocket_continuity_observation_reuses_transport_snapshot_sources_only() {
let worker = include_str!("../src/runtime_resources.rs");
assert!(worker.matches("session.snapshot_source()").count() >= 3);
assert!(worker.contains("observe_websocket_session_snapshot"));
assert!(worker.contains("observe_websocket_post_incident_slot"));
assert!(!worker.contains("tokio_tungstenite::connect_async"));
assert!(!worker.contains("WebSocketStream<"));
assert!(!worker.contains("set_from_slot("));
return;
}
#[test]
fn v0_3_14_pre_004_yellowstone_replay_evidence_is_transport_owned_and_fail_closed_without_coverage_proof() {
let worker = include_str!("../src/runtime_resources.rs");
let transport = include_str!("../../ksp-onchain-transport-lib/src/grpc_stream.rs");
for required in [
"snapshot.replay_delivery_count()",
"snapshot.replay_coverage_unproven_count()",
"source_replay_delivery_total",
"source_replay_coverage_unproven_total",
"source.replay_coverage_unproven",
] {
assert!(worker.contains(required), "required pre.004 Worker replay-evidence guard missing: {required}");
}
for required in [
"replay_delivery_count",
"replay_coverage_unproven_count",
"pending_replay_from_slot",
"tracker.begin_replay(effective_from_slot)",
"first_available > requested",
] {
assert!(transport.contains(required), "required pre.004 Transport replay-evidence guard missing: {required}");
}
for forbidden in ["subscribe_replay_info(", "set_from_slot(", "YellowstoneReplayInfo", "last_requested_from_slot()"] {
assert!(!worker.contains(forbidden), "pre.004 Worker took replay ownership: {forbidden}");
}
assert!(!transport.contains("replay_covered_count"));
assert!(!transport.contains("replay_coverage_proven_count"));
return;
}
#[test]
fn v0_3_14_pre_005_redundant_coverage_requires_exact_or_superset_proof_over_full_range() {
let continuity = include_str!("../src/continuity.rs");
let root = include_str!("../src/lib.rs");
for required in [
"RawTransactionIngestCoverageRelation",
"RawTransactionIngestCoverageEpoch",
"RawTransactionIngestCoverageEpochLedger",
"MAX_RAW_TRANSACTION_INGEST_COVERAGE_EPOCHS",
"self.source_key == target_source_key",
"self.commitment != requirement.commitment",
"!self.range.contains_gap(gap)",
"family_code == required_family_code && fingerprint == required_fingerprint",
"RawTransactionIngestCoverageRelation::Superset",
] {
assert!(continuity.contains(required), "required pre.005 conservative coverage guard missing: {required}");
}
for forbidden in [
"standard_logs\", \"helius_transaction",
"helius_transaction\", \"standard_logs",
"commitment >= requirement.commitment",
"commitment <= requirement.commitment",
"source_key == target_source_key &&",
] {
assert!(!continuity.contains(forbidden), "pre.005 introduced opportunistic coverage equivalence: {forbidden}");
}
for forbidden_public in [
"pub use self::continuity::RawTransactionIngestCoverageRelation",
"pub use self::continuity::RawTransactionIngestCoverageEpoch",
"pub use self::continuity::RawTransactionIngestTargetCoverage",
] {
assert!(!root.contains(forbidden_public), "pre.005 leaked private continuity proof type: {forbidden_public}");
}
return;
}
#[test]
fn v0_3_14_pre_006_http_discovery_is_bounded_prefers_closed_range_and_never_uses_tip_alone_as_coverage() {
let resources = include_str!("../src/runtime_resources.rs");
let root = include_str!("../src/lib.rs");
for required in [
"RawTransactionIngestHttpDiscoveryStrategy",
"RawTransactionIngestHttpDiscoveryWindow",
"RawTransactionIngestHttpScanCapabilities",
"ClosedRange",
"WithLimitBoundary",
"MAX_RAW_TRANSACTION_INGEST_CONTINUITY_DISCOVERY_WINDOW_SLOTS",
"pool.get_blocks(role, start_slot, std::option::Option::Some(end_slot)",
"pool.get_blocks_with_limit(role, start_slot, slot_count",
"self.http_pool.get_block_observed",
"discovered.last().copied().map(|slot| return slot.min(end_slot))",
] {
assert!(resources.contains(required), "required pre.006 bounded HTTP discovery guard missing: {required}");
}
assert!(resources.contains("http_role_scan_capabilities"));
assert!(resources.contains("http_role_supports_repair_scan"));
assert!(!resources.contains("ksp_job_backfill_lib::"));
assert!(!resources.contains("get_blocks_with_limit(role, start_slot, u64::MAX"));
assert!(!root.contains("repair"), "pre.006 leaked lower-case repair responsibility through crate root");
return;
}
#[test]
fn v0_3_14_pre_007_known_reference_hydration_reuses_one_registry_and_preserves_missing_obligation() {
let continuity = include_str!("../src/continuity.rs");
let resources = include_str!("../src/runtime_resources.rs");
let root = include_str!("../src/lib.rs");
for required in [
"RawTransactionIngestKnownReferenceObligation",
"known_reference_processed_unsupported",
"RawTransactionIngestKnownReferenceHydrationResolution",
"RawTransactionIngestKnownReferenceMissingDisposition",
"fetch_hydration_shared",
"resolve_known_reference_hydration",
"PreferBlockSlot",
"AwaitCoverage",
"get_transaction_observed",
"http_role_supports_rpc_method",
] {
assert!(continuity.contains(required) || resources.contains(required), "required pre.007 known-reference guard missing: {required}");
}
assert_eq!(resources.matches("struct RawTransactionIngestGlobalHydrationRegistry").count(), 1);
assert!(!resources.contains("KnownReferenceHydrationRegistry"));
assert!(!resources.contains("known_reference_retry"));
assert!(!root.contains("pub use self::continuity::RawTransactionIngestKnownReferenceObligation"));
assert!(!root.contains("repair"), "pre.007 leaked lower-case repair responsibility through crate root");
return;
}
#[test]
fn v0_3_14_pre_008_reconciliation_and_source_loss_are_target_coverage_gated_without_respawn() {
let continuity = include_str!("../src/continuity.rs");
let resources = include_str!("../src/runtime_resources.rs");
let root = include_str!("../src/lib.rs");
for required in [
"RawTransactionIngestSourceLossDecision",
"source_loss_decision",
"is_covered_by_active_sources",
"reconcile_with_coverage_epochs",
"record_coverage_epoch",
"record_known_reference_gap",
"record_source_loss_gap",
"continuity_frontier",
"has_open_gaps",
"continuity.source_loss_active_set_invalid",
"RawTransactionIngestSourceLossDecision::Fault",
"RawTransactionIngestSourceLossDecision::Continue",
] {
assert!(continuity.contains(required), "required pre.008 reconciliation guard missing: {required}");
}
for required in [
"source_loss_is_reconcilable",
"inventory.supervisor_state()",
"source_loss_continuity_range",
"contracts.record_known_reference_gap",
"contracts.record_source_loss_gap",
"contracts.source_loss_decision",
"RawTransactionIngestSourceLossDecision::Continue",
"RawTransactionIngestSourceLossDecision::Fault",
"source.websocket_incident_unbounded",
"source.replay_coverage_unproven",
] {
assert!(resources.contains(required), "required pre.008 supervisor gate missing: {required}");
}
assert!(!resources.contains("respawn_source"));
assert!(!resources.contains("restart_source"));
assert!(!root.contains("pub use self::continuity::RawTransactionIngestSourceLossDecision"));
assert!(!root.contains("repair"), "pre.008 leaked lower-case repair responsibility through crate root");
return;
}
#[test]
fn v0_3_14_pre_009_health_policy_is_present_future_coverage_gated_and_source_neutral() {
let continuity = include_str!("../src/continuity.rs");
let resources = include_str!("../src/runtime_resources.rs");
let snapshot = include_str!("../src/snapshot.rs");
let root = include_str!("../src/lib.rs");
for required in [
"health_projection",
"future_target_coverage",
"continuity_frontier",
"has_open_gaps",
"is_covered_by_active_sources",
"source_failures_reconciled",
"failed_source_losses_reconciled",
] {
assert!(continuity.contains(required) || snapshot.contains(required), "required pre.009 coverage-health guard missing: {required}");
}
for required in [
"continuity_policy_observed",
"continuity_has_open_gaps",
"snapshot.continuity_frontier_slot != snapshot.processing_frontier_slot",
"!snapshot.future_target_coverage",
"snapshot.source_reconnecting > 0",
"snapshot.source_active == snapshot.source_total",
"ksp_worker_api::WorkerHealth::Healthy",
"ksp_worker_api::WorkerHealth::Degraded",
"ksp_worker_api::WorkerHealth::Unhealthy",
"ksp_worker_api::WorkerState::Faulted",
] {
assert!(snapshot.contains(required), "required pre.009 health projection guard missing: {required}");
}
for required in [
"continuity_contracts",
"contracts.health_projection",
"inventory.active_source_keys()",
"inventory.failed_source_keys()",
"aggregate.with_continuity_health",
"with_failed_source_losses_reconciled",
] {
assert!(resources.contains(required), "required pre.009 inventory-health bridge missing: {required}");
}
assert!(!root.contains("SourceHealthByKey"));
assert!(!root.contains("provider_health"));
assert!(!root.contains("endpoint_health"));
assert!(!root.contains("pub use self::continuity::RawTransactionIngestTargetCoverage"));
assert!(!snapshot.contains("WorkerHealth::Faulted"), "Faulted must remain a Worker lifecycle state rather than a new health enum variant");
return;
}
#[test]
fn v0_3_14_pre_010_repair_fairness_shares_existing_bounds_without_second_pipeline() {
let continuity = include_str!("../src/continuity.rs");
let resources = include_str!("../src/runtime_resources.rs");
let admission = include_str!("../src/admission.rs");
let root = include_str!("../src/lib.rs");
for required in [
"MAX_RAW_TRANSACTION_INGEST_REPAIR_BURST: usize = 1",
"RawTransactionIngestTrafficClass",
"RawTransactionIngestFairTurnGate",
"RawTransactionIngestTrafficClass::Nominal",
"RawTransactionIngestTrafficClass::Repair",
"acquire_hydration_permit",
"validate_repair_fairness_contract",
"repair_block_fetch_limit",
] {
assert!(resources.contains(required) || continuity.contains(required), "required pre.010 fairness guard missing: {required}");
}
assert!(continuity.contains("MAX_RAW_TRANSACTION_INGEST_REPAIR_BLOCK_FETCH_IN_FLIGHT: usize = 4"));
assert!(continuity.contains("MAX_RAW_TRANSACTION_INGEST_REPAIR_DISCOVERY_WINDOW_SLOTS: u64 = 512"));
assert!(admission.contains("tokio::sync::mpsc::channel(capacity)"));
assert!(!resources.contains("tokio::sync::mpsc::channel("), "pre.010 created a second admission pipeline");
assert_eq!(resources.matches("struct RawTransactionIngestGlobalHydrationRegistry").count(), 1);
assert!(!resources.contains("RepairHydrationRegistry"));
assert!(!root.contains("RawTransactionIngestTrafficClass"));
assert!(!root.contains("RawTransactionIngestFairTurnGate"));
return;
}
#[test]
fn v0_3_14_pre_011_gap_observability_is_bounded_checked_and_redacted() {
let continuity = include_str!("../src/continuity.rs");
let snapshot = include_str!("../src/snapshot.rs");
let root = include_str!("../src/lib.rs");
for required in [
"pub struct RawTransactionIngestGapId",
"pub enum RawTransactionIngestGapState",
"pub enum RawTransactionIngestGapReason",
"pub enum RawTransactionIngestRepairMethod",
"pub struct RawTransactionIngestGapSnapshot",
"open_gap_count",
"repairing_gap_count",
"repaired_gap_total",
"unresolved_gap_total",
"replay_repair_total",
"redundant_coverage_repair_total",
"http_scan_repair_total",
"repair_block_fetch_total",
"repair_transaction_hydration_total",
"oldest_open_gap_start_slot",
] {
assert!(snapshot.contains(required), "required pre.011 snapshot observability guard missing: {required}");
}
for required in [
"MAX_RAW_TRANSACTION_INGEST_OPEN_REPAIR_GAPS: usize = 64",
"std::vec::Vec::with_capacity(MAX_RAW_TRANSACTION_INGEST_OPEN_REPAIR_GAPS)",
"gaps.len() == MAX_RAW_TRANSACTION_INGEST_OPEN_REPAIR_GAPS",
"counter_exhausted_error(\"continuity.repaired_gap_total\")",
"counter_exhausted_error(\"continuity.unresolved_gap_total\")",
"gap.last_method = std::option::Option::Some(crate::RawTransactionIngestRepairMethod::RedundantCoverage)",
] {
assert!(continuity.contains(required), "required pre.011 bounded/checked continuity guard missing: {required}");
}
for required in [
"RawTransactionIngestGapId",
"RawTransactionIngestGapReason",
"RawTransactionIngestGapSnapshot",
"RawTransactionIngestGapState",
"RawTransactionIngestRepairMethod",
] {
assert!(root.contains(required), "required pre.011 public source-neutral type missing: {required}");
}
for forbidden in ["source_key:", "endpoint_url:", "signature:", "payload:", "provider_error:"] {
assert!(!snapshot.contains(forbidden), "pre.011 snapshot leaked sensitive/source-specific field: {forbidden}");
}
assert!(!root.contains("source_key"));
return;
}
#[test]
fn v0_3_14_pre_012_shutdown_races_are_bounded_across_source_and_worker_drains() {
let runtime = include_str!("../src/runtime.rs");
let resources = include_str!("../src/runtime_resources.rs");
for required in [
"tokio::time::timeout(settings.shutdown_drain_timeout(), drain).await",
"persistence.abort_all()",
"while persistence.join_next().await.is_some() {}",
"while children.join_next().await.is_some() {}",
] {
assert!(runtime.contains(required), "required pre.012 outer shutdown guard missing: {required}");
}
for required in [
"shutdown_drain_timeout: std::time::Duration",
"tokio::time::timeout(shutdown_drain_timeout, drain).await",
"children.abort_all()",
"ERROR_CODE_RAW_TRANSACTION_INGEST_DRAIN_TIMEOUT",
"coordinator.abort_all(&mut processing_frontier).await",
"discover_http_block_window(",
"admission_sender.send(ingress)",
"if *stop_receiver.borrow()",
] {
assert!(resources.contains(required), "required pre.012 source shutdown guard missing: {required}");
}
for forbidden in [
"unbounded_channel",
"ksp_store_postgres_lib::",
"ksp_config_lib::",
"ksp_job_backfill_lib::",
"reqwest::",
"tokio_tungstenite::",
"tonic::",
"yellowstone_grpc_proto::",
] {
assert!(!runtime.contains(forbidden) && !resources.contains(forbidden), "pre.012 crossed a Worker/facade boundary: {forbidden}");
}
return;
}
#[test]
fn v0_3_14_pre_013_cross_layer_completeness_security_closure_is_exact_and_redacted() {
let cross_layer = include_str!("cross_layer_completeness.rs");
let dependency_boundary = include_str!("dependency_boundary.rs");
let public_api = include_str!("public_api.rs");
for required in [
"v0_3_14_pre_013_gap_repair_closure_keeps_one_transport_worker_common_raw_store_pipeline",
"v0_3_14_pre_013_security_redaction_covers_gap_repair_and_terminal_paths",
"v0_3_14_pre_013_legacy_v0_v1_remains_proven_after_gap_repair_hardening",
"v0_3_14_pre_013_worker_and_backfill_remain_independent_producers",
] {
assert!(cross_layer.contains(required), "pre.013 cross-layer hardening proof missing: {required}");
}
assert!(dependency_boundary.contains("v0_3_14_pre_013_cross_layer_closure_keeps_dependency_and_producer_boundaries_exact"));
assert!(public_api.contains("v0_3_14_pre_013_completeness_closure_adds_no_public_surface"));
let root = include_str!("../src/lib.rs");
for forbidden in ["source_key", "provider_gap", "endpoint_gap", "signature_gap", "payload_gap", "pub mod "] {
assert!(!root.contains(forbidden), "pre.013 public Worker root leaked forbidden material: {forbidden}");
}
return;
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/tests/public_api.rs // file: crates/ksp-worker-raw-transaction-ingest-lib/tests/public_api.rs
// version: 22 // version: 25
//! External public-surface proofs for the RAW transaction ingest Worker foundation. //! External public-surface proofs for the RAW transaction ingest Worker foundation.
@@ -428,3 +428,87 @@ fn v0_3_13_pre_012_completeness_closure_adds_no_public_implementation_surface()
assert!(cross_layer.contains("v0_3_13_pre_012_legacy_v0_v1_is_proven_from_transport_through_worker_common_raw_to_store")); assert!(cross_layer.contains("v0_3_13_pre_012_legacy_v0_v1_is_proven_from_transport_through_worker_common_raw_to_store"));
return; return;
} }
#[test]
fn v0_3_14_pre_011_gap_observability_is_public_bounded_and_source_neutral() {
let _gap_id_value: fn(ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapId) -> u64 =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapId::value;
let _gap_id: fn(
&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapSnapshot,
) -> ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapId = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapSnapshot::gap_id;
let _start_slot: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapSnapshot) -> u64 =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapSnapshot::start_slot;
let _end_slot: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapSnapshot) -> u64 =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapSnapshot::end_slot;
let _state: fn(
&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapSnapshot,
) -> ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapState = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapSnapshot::state;
let _reason: fn(
&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapSnapshot,
) -> ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapReason = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapSnapshot::reason;
let _last_method: fn(
&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapSnapshot,
) -> std::option::Option<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRepairMethod> =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapSnapshot::last_method;
let _gaps: fn(
&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot,
) -> &[ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestGapSnapshot] = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::gaps;
let _open_gap_count: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> usize =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::open_gap_count;
let _repairing_gap_count: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> usize =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::repairing_gap_count;
let _repaired_gap_total: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> u64 =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::repaired_gap_total;
let _unresolved_gap_total: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> u64 =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::unresolved_gap_total;
let _replay_repair_total: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> u64 =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::replay_repair_total;
let _redundant_coverage_repair_total: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> u64 =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::redundant_coverage_repair_total;
let _http_scan_repair_total: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> u64 =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::http_scan_repair_total;
let _repair_block_fetch_total: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> u64 =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::repair_block_fetch_total;
let _repair_transaction_hydration_total: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> u64 =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::repair_transaction_hydration_total;
let _oldest_open_gap_start_slot: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> std::option::Option<u64> =
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::oldest_open_gap_start_slot;
let root = include_str!("../src/lib.rs");
for forbidden in ["SourceGapByKey", "provider_gap", "endpoint_gap", "signature_gap", "payload_gap", "source_key"] {
assert!(!root.contains(forbidden), "pre.011 public root leaked source-specific gap material: {forbidden}");
}
return;
}
#[test]
fn v0_3_14_pre_013_completeness_closure_adds_no_public_surface() {
let root = include_str!("../src/lib.rs");
let public_exports = root.lines().filter(|line| return line.trim().starts_with("pub use ")).count();
assert_eq!(public_exports, 43);
for required in [
"RawTransactionIngestGapId",
"RawTransactionIngestGapReason",
"RawTransactionIngestGapSnapshot",
"RawTransactionIngestGapState",
"RawTransactionIngestRepairMethod",
"RawTransactionIngestSnapshot",
"RawTransactionIngestWorker",
] {
assert!(root.contains(required), "pre.013 expected stable public Worker surface missing: {required}");
}
for forbidden in [
"RawTransactionIngestContinuityContracts",
"RawTransactionIngestGlobalHydrationRegistry",
"RawTransactionIngestSourceInventory",
"RawTransactionIngestTrafficClass",
"RawTransactionIngestFairTurnGate",
"RawTransactionIngestSourceLossDecision",
] {
assert!(
!root.lines().any(|line| return line.trim().starts_with("pub use ") && line.contains(forbidden)),
"pre.013 private type leaked publicly: {forbidden}"
);
}
assert!(!root.contains("pub mod "));
return;
}

View File

@@ -1,7 +1,7 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs // file: crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
// version: 20 // version: 32
//! Release-completeness canaries through the `pre.012` cross-layer completeness/security tranche. //! Release-completeness canaries through the `v0.3.14-pre.013` cross-layer closure tranche.
#[test] #[test]
fn pre_010_production_module_inventory_is_exact() -> std::io::Result<()> { fn pre_010_production_module_inventory_is_exact() -> std::io::Result<()> {
@@ -34,13 +34,24 @@ fn pre_010_production_module_inventory_is_exact() -> std::io::Result<()> {
names.sort_unstable(); names.sort_unstable();
assert_eq!( assert_eq!(
names, names,
std::vec!["admission.rs", "error.rs", "identity.rs", "lib.rs", "persistence.rs", "runtime.rs", "runtime_resources.rs", "settings.rs", "snapshot.rs",] std::vec![
"admission.rs",
"continuity.rs",
"error.rs",
"identity.rs",
"lib.rs",
"persistence.rs",
"runtime.rs",
"runtime_resources.rs",
"settings.rs",
"snapshot.rs",
]
); );
return std::result::Result::Ok(()); return std::result::Result::Ok(());
} }
#[test] #[test]
fn pre_010_public_root_export_inventory_is_exact() { fn v0_3_14_pre_011_public_root_export_inventory_is_exact() {
let root = include_str!("../src/lib.rs"); let root = include_str!("../src/lib.rs");
let mut exports = std::vec::Vec::new(); let mut exports = std::vec::Vec::new();
for line in root.lines() { for line in root.lines() {
@@ -83,9 +94,14 @@ fn pre_010_public_root_export_inventory_is_exact() {
"MIN_RAW_TRANSACTION_INGEST_PERSISTENCE_CONCURRENCY", "MIN_RAW_TRANSACTION_INGEST_PERSISTENCE_CONCURRENCY",
"MIN_RAW_TRANSACTION_INGEST_SHUTDOWN_DRAIN_TIMEOUT", "MIN_RAW_TRANSACTION_INGEST_SHUTDOWN_DRAIN_TIMEOUT",
"RAW_TRANSACTION_INGEST_WORKER_KIND_CODE", "RAW_TRANSACTION_INGEST_WORKER_KIND_CODE",
"RawTransactionIngestGapId",
"RawTransactionIngestGapReason",
"RawTransactionIngestGapSnapshot",
"RawTransactionIngestGapState",
"RawTransactionIngestHandle", "RawTransactionIngestHandle",
"RawTransactionIngestHeliusTransactionSource", "RawTransactionIngestHeliusTransactionSource",
"RawTransactionIngestHttpBlockPollingSource", "RawTransactionIngestHttpBlockPollingSource",
"RawTransactionIngestRepairMethod",
"RawTransactionIngestRuntimeResources", "RawTransactionIngestRuntimeResources",
"RawTransactionIngestSettings", "RawTransactionIngestSettings",
"RawTransactionIngestSnapshot", "RawTransactionIngestSnapshot",
@@ -132,6 +148,8 @@ fn pre_010_external_hardening_suite_is_present_and_scoped() {
"v0_3_13_pre_009_duplicate_storm_disagreement_and_starvation_guards_are_explicit", "v0_3_13_pre_009_duplicate_storm_disagreement_and_starvation_guards_are_explicit",
"v0_3_13_pre_010_multi_source_health_is_conservative_counted_and_redacted", "v0_3_13_pre_010_multi_source_health_is_conservative_counted_and_redacted",
"v0_3_13_pre_011_shutdown_races_are_bounded_joined_atomic_and_counter_safe", "v0_3_13_pre_011_shutdown_races_are_bounded_joined_atomic_and_counter_safe",
"v0_3_14_pre_002_continuity_contracts_are_private_bounded_and_io_free",
"v0_3_14_pre_003_websocket_continuity_observation_reuses_transport_snapshot_sources_only",
] { ] {
assert!(hardening.contains(required), "required pre.010 hardening canary missing: {required}"); assert!(hardening.contains(required), "required pre.010 hardening canary missing: {required}");
} }
@@ -235,3 +253,213 @@ fn v0_3_13_pre_012_cross_layer_completeness_security_inventory_is_exact() {
assert!(public_api.contains("v0_3_13_pre_012_completeness_closure_adds_no_public_implementation_surface")); assert!(public_api.contains("v0_3_13_pre_012_completeness_closure_adds_no_public_implementation_surface"));
return; return;
} }
#[test]
fn v0_3_14_pre_004_native_replay_evidence_canaries_are_present_without_public_replay_ownership() {
let hardening = include_str!("hardening.rs");
let resources = include_str!("../src/runtime_resources.rs");
let resource_tests = include_str!("../unit_tests/runtime_resources.rs");
let root = include_str!("../src/lib.rs");
assert!(hardening.contains("v0_3_14_pre_004_yellowstone_replay_evidence_is_transport_owned_and_fail_closed_without_coverage_proof"));
assert!(resource_tests.contains("v0_3_14_pre_004_replay_delivery_is_not_coverage_and_unproven_coverage_fails_closed"));
assert!(resources.contains("source.replay_coverage_unproven"));
assert!(!root.contains("ReplayInfo"));
assert!(!root.contains("from_slot"));
assert!(!root.contains("set_from_slot"));
return;
}
#[test]
fn v0_3_14_pre_005_redundant_coverage_canaries_are_present_without_public_surface_growth() {
let continuity_tests = include_str!("../unit_tests/continuity.rs");
let hardening = include_str!("hardening.rs");
let root = include_str!("../src/lib.rs");
for required in [
"pre_005_scope_relations_are_exact_superset_and_cross_family_conservative",
"pre_005_coverage_epoch_requires_distinct_source_same_commitment_and_full_range",
"pre_005_cross_family_exact_scopes_never_become_redundant_by_fingerprint_alone",
"pre_005_coverage_epoch_ledger_is_bounded_monotone_and_capability_bound",
] {
assert!(continuity_tests.contains(required), "required pre.005 continuity proof canary missing: {required}");
}
assert!(hardening.contains("v0_3_14_pre_005_redundant_coverage_requires_exact_or_superset_proof_over_full_range"));
assert!(!root.contains("pub use self::continuity::RawTransactionIngestCoverageEpoch"));
assert!(!root.contains("pub use self::continuity::RawTransactionIngestCoverageRelation"));
return;
}
#[test]
fn v0_3_14_pre_006_bounded_http_discovery_canaries_are_present_without_backfill_or_public_surface_growth() {
let hardening = include_str!("hardening.rs");
let resource_tests = include_str!("../unit_tests/runtime_resources.rs");
let root = include_str!("../src/lib.rs");
for required in [
"v0_3_14_pre_006_http_scan_capabilities_prefer_closed_range_and_require_safe_fallback",
"v0_3_14_pre_006_http_scan_capability_detection_is_role_local_and_io_free",
"v0_3_14_pre_006_http_discovery_window_is_inclusive_bounded_and_overflow_safe",
"v0_3_14_pre_006_closed_range_discovery_proves_skipped_slots_only_inside_exact_window",
"v0_3_14_pre_006_with_limit_discovery_never_uses_tip_alone_as_tail_coverage_proof",
] {
assert!(resource_tests.contains(required), "required pre.006 HTTP discovery canary missing: {required}");
}
assert!(hardening.contains("v0_3_14_pre_006_http_discovery_is_bounded_prefers_closed_range_and_never_uses_tip_alone_as_coverage"));
assert!(!root.contains("pub use self::runtime_resources::RawTransactionIngestHttpDiscovery"));
assert!(!root.contains("backfill"));
return;
}
#[test]
fn v0_3_14_pre_007_known_reference_hydration_canaries_are_present_without_second_registry_or_public_surface_growth() {
let continuity_tests = include_str!("../unit_tests/continuity.rs");
let hardening = include_str!("hardening.rs");
let resource_tests = include_str!("../unit_tests/runtime_resources.rs");
let root = include_str!("../src/lib.rs");
for required in [
"pre_007_known_reference_obligation_preserves_exact_reference_slot_and_commitment",
"v0_3_14_pre_007_known_reference_hydration_reuses_global_registry_and_available_material",
"v0_3_14_pre_007_missing_known_reference_remains_open_without_block_capability",
"v0_3_14_pre_007_missing_known_reference_prefers_same_role_block_slot_when_supported",
] {
assert!(continuity_tests.contains(required) || resource_tests.contains(required), "required pre.007 canary missing: {required}");
}
assert!(hardening.contains("v0_3_14_pre_007_known_reference_hydration_reuses_one_registry_and_preserves_missing_obligation"));
assert!(!root.contains("pub use self::continuity::RawTransactionIngestKnownReferenceObligation"));
assert!(!root.contains("pub use self::runtime_resources::RawTransactionIngestKnownReference"));
return;
}
#[test]
fn v0_3_14_pre_008_reconciliation_canaries_are_present_without_public_surface_or_respawn_growth() {
let continuity_tests = include_str!("../unit_tests/continuity.rs");
let hardening = include_str!("hardening.rs");
let resource_tests = include_str!("../unit_tests/runtime_resources.rs");
let root = include_str!("../src/lib.rs");
for required in [
"pre_008_continuity_frontier_is_gap_aware_and_recovers_after_redundant_proof",
"pre_008_source_loss_requires_full_active_target_coverage",
"pre_008_open_gap_without_proven_epoch_blocks_redundant_source_continuation",
"pre_008_known_reference_missing_moves_to_continuity_ledger_and_reconciles_from_proven_full_ledger_epoch",
"v0_3_14_pre_008_proven_full_ledger_epoch_survives_filtered_peer_loss_without_worker_respawn",
"v0_3_14_pre_008_only_classified_source_loss_enters_coverage_decision",
] {
assert!(continuity_tests.contains(required) || resource_tests.contains(required), "required pre.008 canary missing: {required}");
}
assert!(hardening.contains("v0_3_14_pre_008_reconciliation_and_source_loss_are_target_coverage_gated_without_respawn"));
assert!(!root.contains("pub use self::continuity::RawTransactionIngestSourceLossDecision"));
return;
}
#[test]
fn v0_3_14_pre_009_health_policy_canaries_are_present_without_public_source_identity_growth() {
let continuity_tests = include_str!("../unit_tests/continuity.rs");
let hardening = include_str!("hardening.rs");
let resource_tests = include_str!("../unit_tests/runtime_resources.rs");
let snapshot_tests = include_str!("../unit_tests/snapshot.rs");
let root = include_str!("../src/lib.rs");
for required in [
"pre_009_health_projection_distinguishes_future_coverage_from_open_gap_reconciliation",
"pre_009_health_projection_rejects_duplicate_and_unknown_active_sources",
"v0_3_14_pre_009_inventory_health_projection_tracks_reconciled_coverage",
"v0_3_14_pre_009_health_requires_present_and_future_coverage_before_healthy",
] {
assert!(
continuity_tests.contains(required) || resource_tests.contains(required) || snapshot_tests.contains(required),
"required pre.009 health canary missing: {required}"
);
}
assert!(hardening.contains("v0_3_14_pre_009_health_policy_is_present_future_coverage_gated_and_source_neutral"));
assert!(!root.contains("SourceHealthByKey"));
assert!(!root.contains("pub use self::continuity::RawTransactionIngestTargetCoverage"));
return;
}
#[test]
fn v0_3_14_pre_010_repair_fairness_canaries_are_present_without_public_or_pipeline_growth() {
let hardening = include_str!("hardening.rs");
let resource_tests = include_str!("../unit_tests/runtime_resources.rs");
let root = include_str!("../src/lib.rs");
for required in [
"v0_3_14_pre_010_fair_scheduler_alternates_nominal_and_repair_with_bounded_burst",
"v0_3_14_pre_010_fair_waiter_bound_is_checked_and_released_without_reserved_capacity",
"v0_3_14_pre_010_nominal_and_repair_hydration_share_one_capacity_one_permit_pool",
"v0_3_14_pre_010_repair_only_progresses_when_existing_capacity_is_one",
] {
assert!(resource_tests.contains(required), "required pre.010 fairness canary missing: {required}");
}
assert!(hardening.contains("v0_3_14_pre_010_repair_fairness_shares_existing_bounds_without_second_pipeline"));
assert!(!root.contains("RawTransactionIngestTrafficClass"));
assert!(!root.contains("RawTransactionIngestFairTurnGate"));
return;
}
#[test]
fn v0_3_14_pre_011_gap_observability_canaries_are_present_without_source_identity_growth() {
let continuity_tests = include_str!("../unit_tests/continuity.rs");
let hardening = include_str!("hardening.rs");
let public_api = include_str!("public_api.rs");
let snapshot_tests = include_str!("../unit_tests/snapshot.rs");
let root = include_str!("../src/lib.rs");
for required in [
"pre_011_gap_observability_projects_open_and_recent_repaired_entries",
"v0_3_14_pre_011_snapshot_carries_checked_gap_observability",
"v0_3_14_pre_011_gap_observability_is_public_bounded_and_source_neutral",
] {
assert!(
continuity_tests.contains(required) || snapshot_tests.contains(required) || public_api.contains(required),
"required pre.011 observability canary missing: {required}"
);
}
assert!(hardening.contains("v0_3_14_pre_011_gap_observability_is_bounded_checked_and_redacted"));
for required in [
"RawTransactionIngestGapId",
"RawTransactionIngestGapReason",
"RawTransactionIngestGapSnapshot",
"RawTransactionIngestGapState",
"RawTransactionIngestRepairMethod",
] {
assert!(root.contains(required), "required pre.011 public type missing: {required}");
}
assert!(!root.contains("source_key"));
return;
}
#[test]
fn v0_3_14_pre_012_shutdown_race_canaries_cover_bounded_source_and_worker_drains() {
let dependency_boundary = include_str!("dependency_boundary.rs");
let hardening = include_str!("hardening.rs");
let resource_tests = include_str!("../unit_tests/runtime_resources.rs");
let root = include_str!("../src/lib.rs");
for required in [
"v0_3_14_pre_012_source_fault_preserves_first_fault_and_aborts_non_cooperative_sibling",
"v0_3_14_pre_012_stop_aborts_non_cooperative_source_after_bounded_drain",
] {
assert!(resource_tests.contains(required), "required pre.012 source shutdown canary missing: {required}");
}
assert!(hardening.contains("v0_3_14_pre_012_shutdown_races_are_bounded_across_source_and_worker_drains"));
assert!(dependency_boundary.contains("v0_3_14_pre_012_shutdown_hardening_stays_worker_local_and_facade_only"));
assert!(!root.contains("RawTransactionIngestShutdown"));
assert!(!root.contains("SourceDrain"));
return;
}
#[test]
fn v0_3_14_pre_013_cross_layer_closure_canaries_are_complete_without_behavior_or_surface_growth() {
let cross_layer = include_str!("cross_layer_completeness.rs");
let dependency_boundary = include_str!("dependency_boundary.rs");
let hardening = include_str!("hardening.rs");
let public_api = include_str!("public_api.rs");
for required in [
"v0_3_14_pre_013_gap_repair_closure_keeps_one_transport_worker_common_raw_store_pipeline",
"v0_3_14_pre_013_security_redaction_covers_gap_repair_and_terminal_paths",
"v0_3_14_pre_013_legacy_v0_v1_remains_proven_after_gap_repair_hardening",
"v0_3_14_pre_013_worker_and_backfill_remain_independent_producers",
] {
assert!(cross_layer.contains(required), "required pre.013 cross-layer canary missing: {required}");
}
assert!(dependency_boundary.contains("v0_3_14_pre_013_cross_layer_closure_keeps_dependency_and_producer_boundaries_exact"));
assert!(hardening.contains("v0_3_14_pre_013_cross_layer_completeness_security_closure_is_exact_and_redacted"));
assert!(public_api.contains("v0_3_14_pre_013_completeness_closure_adds_no_public_surface"));
assert!(include_str!("../src/lib.rs").contains("RawTransactionIngestGapSnapshot"));
assert!(include_str!("../src/lib.rs").contains("RawTransactionIngestWorker"));
return;
}

View File

@@ -0,0 +1,727 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/continuity.rs
// version: 8
fn network() -> std::option::Option<ksp_store_lib::RawNetworkId> {
return match ksp_store_lib::RawNetworkId::new("mainnet") {
std::result::Result::Ok(value) => std::option::Option::Some(value),
std::result::Result::Err(_) => std::option::Option::None,
};
}
fn exact_scope(family: &'static str, fingerprint_byte: u8) -> crate::RawTransactionIngestCoverageScope {
return crate::RawTransactionIngestCoverageScope::exact_source_scope(family, [fingerprint_byte; 32]);
}
fn capability(
source_key_byte: u8,
commitment: ksp_onchain_transport_lib::SolanaCommitment,
scope: crate::RawTransactionIngestCoverageScope,
) -> std::option::Option<crate::RawTransactionIngestContinuityCapabilityDescriptor> {
let network = match network() {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::option::Option::None,
};
return match crate::RawTransactionIngestContinuityCapabilityDescriptor::new(
[source_key_byte; 32],
network,
commitment,
scope,
true,
false,
false,
true,
false,
false,
) {
std::result::Result::Ok(value) => std::option::Option::Some(value),
std::result::Result::Err(_) => std::option::Option::None,
};
}
fn gap(
gap_id: u64,
source_key_byte: u8,
start_slot: u64,
end_slot: u64,
state: super::RawTransactionIngestGapState,
) -> std::option::Option<super::RawTransactionIngestGap> {
let network = match network() {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::option::Option::None,
};
let range = match super::RawTransactionIngestGapRange::new(start_slot, end_slot) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::option::Option::None,
};
return std::option::Option::Some(super::RawTransactionIngestGap {
commitment: ksp_onchain_transport_lib::SolanaCommitment::Confirmed,
coverage_requirement: exact_scope("standard_logs", 9),
gap_id: super::RawTransactionIngestGapId(gap_id),
network,
range,
reason: super::RawTransactionIngestGapReason::WebSocketReconnect,
source_key: [source_key_byte; 32],
state,
last_method: std::option::Option::None,
});
}
#[test]
fn pre_002_repair_bounds_are_exact_and_run_local() {
assert_eq!(super::MAX_RAW_TRANSACTION_INGEST_OPEN_REPAIR_GAPS, 64);
assert_eq!(super::MAX_RAW_TRANSACTION_INGEST_REPAIR_ACTIVE_GAPS, 1);
assert_eq!(super::MAX_RAW_TRANSACTION_INGEST_REPAIR_BLOCK_FETCH_IN_FLIGHT, 4);
assert_eq!(super::MAX_RAW_TRANSACTION_INGEST_REPAIR_DISCOVERY_WINDOW_SLOTS, 512);
assert_eq!(super::MAX_RAW_TRANSACTION_INGEST_REPAIR_RANGE_SLOTS, 4_096);
return;
}
#[test]
fn pre_002_gap_range_is_inclusive_bounded_and_overflow_safe() {
assert!(super::RawTransactionIngestGapRange::new(10, 9).is_err());
assert!(super::RawTransactionIngestGapRange::new(10, 10 + super::MAX_RAW_TRANSACTION_INGEST_REPAIR_RANGE_SLOTS - 1).is_ok());
assert!(super::RawTransactionIngestGapRange::new(10, 10 + super::MAX_RAW_TRANSACTION_INGEST_REPAIR_RANGE_SLOTS).is_err());
assert!(super::RawTransactionIngestGapRange::new(u64::MAX, u64::MAX).is_ok());
return;
}
#[test]
fn pre_002_gap_range_overlap_and_adjacency_are_explicit() {
let first = match super::RawTransactionIngestGapRange::new(100, 109) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected first range failure: {error}"),
};
let overlap = match super::RawTransactionIngestGapRange::new(105, 115) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected overlap range failure: {error}"),
};
let adjacent = match super::RawTransactionIngestGapRange::new(110, 120) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected adjacent range failure: {error}"),
};
let separate = match super::RawTransactionIngestGapRange::new(111, 120) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected separate range failure: {error}"),
};
assert!(first.overlaps_or_is_adjacent(overlap));
assert!(first.overlaps_or_is_adjacent(adjacent));
assert!(!first.overlaps_or_is_adjacent(separate));
let merged = first.try_merge(adjacent);
assert!(matches!(merged, std::result::Result::Ok(std::option::Option::Some(value)) if value.start_slot() == 100 && value.end_slot() == 120));
return;
}
#[test]
fn pre_002_target_coverage_deduplicates_exact_scopes_without_cross_commitment_broadening() {
let first = match capability(1, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 7)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("first capability fixture unavailable"),
};
let second = match capability(2, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 7)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("second capability fixture unavailable"),
};
let finalized_full =
match capability(3, ksp_onchain_transport_lib::SolanaCommitment::Finalized, crate::RawTransactionIngestCoverageScope::full_ledger_transactions()) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("full capability fixture unavailable"),
};
let contracts = match crate::RawTransactionIngestContinuityContracts::new(std::vec![first, second, finalized_full]) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("continuity contracts fixture failed: {error}"),
};
assert_eq!(contracts.capabilities.len(), 3);
assert_eq!(contracts.target_coverage.requirements.len(), 2);
assert!(contracts.target_coverage.requirements.iter().any(|requirement| {
return requirement.commitment == ksp_onchain_transport_lib::SolanaCommitment::Confirmed && requirement.scope == exact_scope("standard_logs", 7);
}));
assert!(contracts.target_coverage.requirements.iter().any(|requirement| {
return requirement.commitment == ksp_onchain_transport_lib::SolanaCommitment::Finalized
&& requirement.scope == crate::RawTransactionIngestCoverageScope::full_ledger_transactions();
}));
return;
}
#[test]
fn pre_002_full_ledger_scope_subsumes_only_same_commitment_targets() {
let exact = match capability(1, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("helius_transaction", 4)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("exact capability fixture unavailable"),
};
let full = match capability(2, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, crate::RawTransactionIngestCoverageScope::full_ledger_transactions())
{
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("full capability fixture unavailable"),
};
let contracts = match crate::RawTransactionIngestContinuityContracts::new(std::vec![exact, full]) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("continuity contracts fixture failed: {error}"),
};
assert_eq!(contracts.target_coverage.requirements.len(), 1);
assert_eq!(contracts.target_coverage.requirements[0].scope, crate::RawTransactionIngestCoverageScope::full_ledger_transactions());
assert_eq!(contracts.target_coverage.requirements[0].commitment, ksp_onchain_transport_lib::SolanaCommitment::Confirmed);
return;
}
#[test]
fn pre_002_known_references_can_never_be_configured_target_coverage() {
let network = match network() {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("network fixture unavailable"),
};
let result = crate::RawTransactionIngestContinuityCapabilityDescriptor::new(
[1_u8; 32],
network,
ksp_onchain_transport_lib::SolanaCommitment::Confirmed,
crate::RawTransactionIngestCoverageScope::KnownReferences([2_u8; 32]),
true,
false,
false,
true,
false,
false,
);
assert!(result.is_err());
return;
}
#[test]
fn pre_002_gap_ledger_rejects_coalescible_open_ranges_and_active_overflow() {
let network = match network() {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("network fixture unavailable"),
};
let first = match gap(1, 1, 100, 109, super::RawTransactionIngestGapState::Pending) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("first gap fixture unavailable"),
};
let adjacent = match gap(2, 1, 110, 120, super::RawTransactionIngestGapState::Pending) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("adjacent gap fixture unavailable"),
};
let adjacent_ledger = super::RawTransactionIngestGapLedger { gaps: std::vec![first, adjacent], network: network.clone(), next_gap_id: 3 };
assert!(adjacent_ledger.validate_invariants().is_err());
let first_active = match gap(1, 1, 100, 109, super::RawTransactionIngestGapState::Repairing) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("first active gap fixture unavailable"),
};
let second_active = match gap(2, 2, 200, 209, super::RawTransactionIngestGapState::Repairing) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("second active gap fixture unavailable"),
};
let active_ledger = super::RawTransactionIngestGapLedger { gaps: std::vec![first_active, second_active], network, next_gap_id: 3 };
assert!(active_ledger.validate_invariants().is_err());
return;
}
#[test]
fn pre_002_gap_ledger_enforces_open_gap_bound_and_next_id_monotonicity() {
let network = match network() {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("network fixture unavailable"),
};
let mut gaps = std::vec::Vec::new();
for index in 0..=super::MAX_RAW_TRANSACTION_INGEST_OPEN_REPAIR_GAPS {
let gap_id = (index as u64) + 1;
let slot = (index as u64) * 2;
let gap = match gap(gap_id, 1, slot, slot, super::RawTransactionIngestGapState::Pending) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("gap fixture unavailable"),
};
gaps.push(gap);
}
let ledger = super::RawTransactionIngestGapLedger { gaps, network: network.clone(), next_gap_id: 66 };
assert!(ledger.validate_invariants().is_err());
let single = match gap(7, 1, 100, 100, super::RawTransactionIngestGapState::Unresolved) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("single gap fixture unavailable"),
};
let stale_next = super::RawTransactionIngestGapLedger { gaps: std::vec![single], network, next_gap_id: 7 };
assert!(stale_next.validate_invariants().is_err());
return;
}
#[test]
fn pre_003_websocket_incident_anchor_is_inclusive_monotone_and_bounded() {
let mut anchor = match crate::RawTransactionIngestWebSocketIncidentAnchor::new(100, 1, 0, true, false) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected anchor failure: {error}"),
};
assert_eq!(anchor.start_slot(), 100);
assert_eq!(anchor.end_slot(), std::option::Option::None);
assert!(anchor.saw_reconnect());
assert!(!anchor.saw_overflow());
assert!(anchor.extend(2, 1, true, true).is_ok());
assert!(anchor.saw_reconnect());
assert!(anchor.saw_overflow());
assert!(anchor.extend(1, 1, false, false).is_err());
assert!(anchor.close_at(100 + super::MAX_RAW_TRANSACTION_INGEST_REPAIR_RANGE_SLOTS - 1).is_ok());
assert_eq!(anchor.end_slot(), std::option::Option::Some(100 + super::MAX_RAW_TRANSACTION_INGEST_REPAIR_RANGE_SLOTS - 1));
assert!(anchor.extend(3, 1, true, false).is_err());
return;
}
#[test]
fn pre_003_websocket_incident_anchor_rejects_missing_reason_reversal_and_oversize() {
assert!(crate::RawTransactionIngestWebSocketIncidentAnchor::new(100, 0, 0, false, false).is_err());
let mut reversed = match crate::RawTransactionIngestWebSocketIncidentAnchor::new(100, 0, 1, false, true) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected overflow anchor failure: {error}"),
};
assert!(reversed.close_at(99).is_err());
let mut oversized = match crate::RawTransactionIngestWebSocketIncidentAnchor::new(100, 1, 0, true, false) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected oversize anchor fixture failure: {error}"),
};
assert!(oversized.close_at(100 + super::MAX_RAW_TRANSACTION_INGEST_REPAIR_RANGE_SLOTS).is_err());
return;
}
#[test]
fn pre_005_scope_relations_are_exact_superset_and_cross_family_conservative() {
let exact = exact_scope("standard_logs", 7);
let same = exact_scope("standard_logs", 7);
let different_filter = exact_scope("standard_logs", 8);
let different_family = exact_scope("helius_transaction", 7);
let full = crate::RawTransactionIngestCoverageScope::full_ledger_transactions();
assert_eq!(exact.relation_to(&same), std::option::Option::Some(super::RawTransactionIngestCoverageRelation::Exact));
assert_eq!(full.relation_to(&exact), std::option::Option::Some(super::RawTransactionIngestCoverageRelation::Superset));
assert_eq!(full.relation_to(&full), std::option::Option::Some(super::RawTransactionIngestCoverageRelation::Exact));
assert!(exact.relation_to(&full).is_none());
assert!(exact.relation_to(&different_filter).is_none());
assert!(exact.relation_to(&different_family).is_none());
assert!(crate::RawTransactionIngestCoverageScope::KnownReferences([7_u8; 32]).relation_to(&exact).is_none());
return;
}
#[test]
fn pre_005_coverage_epoch_requires_distinct_source_same_commitment_and_full_range() {
let target_requirement = super::RawTransactionIngestCoverageRequirement {
commitment: ksp_onchain_transport_lib::SolanaCommitment::Confirmed,
scope: exact_scope("standard_logs", 9),
};
let gap = match super::RawTransactionIngestGapRange::new(100, 110) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected gap fixture failure: {error}"),
};
let full_range = match super::RawTransactionIngestCoverageRange::new(90, 120) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected coverage range failure: {error}"),
};
let partial_range = match super::RawTransactionIngestCoverageRange::new(100, 109) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected partial coverage range failure: {error}"),
};
let exact_epoch = match super::RawTransactionIngestCoverageEpoch::new(
1,
[2_u8; 32],
ksp_onchain_transport_lib::SolanaCommitment::Confirmed,
exact_scope("standard_logs", 9),
full_range,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected exact epoch failure: {error}"),
};
let full_epoch = match super::RawTransactionIngestCoverageEpoch::new(
2,
[3_u8; 32],
ksp_onchain_transport_lib::SolanaCommitment::Confirmed,
crate::RawTransactionIngestCoverageScope::full_ledger_transactions(),
full_range,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected full epoch failure: {error}"),
};
let partial_epoch = match super::RawTransactionIngestCoverageEpoch::new(
3,
[4_u8; 32],
ksp_onchain_transport_lib::SolanaCommitment::Confirmed,
crate::RawTransactionIngestCoverageScope::full_ledger_transactions(),
partial_range,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected partial epoch failure: {error}"),
};
let finalized_epoch = match super::RawTransactionIngestCoverageEpoch::new(
4,
[5_u8; 32],
ksp_onchain_transport_lib::SolanaCommitment::Finalized,
crate::RawTransactionIngestCoverageScope::full_ledger_transactions(),
full_range,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected finalized epoch failure: {error}"),
};
assert_eq!(
exact_epoch.relation_to_requirement([1_u8; 32], &target_requirement, gap),
std::option::Option::Some(super::RawTransactionIngestCoverageRelation::Exact),
);
assert_eq!(
full_epoch.relation_to_requirement([1_u8; 32], &target_requirement, gap),
std::option::Option::Some(super::RawTransactionIngestCoverageRelation::Superset),
);
assert!(exact_epoch.relation_to_requirement([2_u8; 32], &target_requirement, gap).is_none());
assert!(partial_epoch.relation_to_requirement([1_u8; 32], &target_requirement, gap).is_none());
assert!(finalized_epoch.relation_to_requirement([1_u8; 32], &target_requirement, gap).is_none());
return;
}
#[test]
fn pre_005_cross_family_exact_scopes_never_become_redundant_by_fingerprint_alone() {
let target_requirement = super::RawTransactionIngestCoverageRequirement {
commitment: ksp_onchain_transport_lib::SolanaCommitment::Confirmed,
scope: exact_scope("standard_logs", 11),
};
let gap = match super::RawTransactionIngestGapRange::new(500, 510) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected gap fixture failure: {error}"),
};
let range = match super::RawTransactionIngestCoverageRange::new(490, 520) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected coverage range failure: {error}"),
};
let epoch = match super::RawTransactionIngestCoverageEpoch::new(
1,
[2_u8; 32],
ksp_onchain_transport_lib::SolanaCommitment::Confirmed,
exact_scope("helius_transaction", 11),
range,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected epoch failure: {error}"),
};
assert!(epoch.relation_to_requirement([1_u8; 32], &target_requirement, gap).is_none());
return;
}
#[test]
fn pre_005_coverage_epoch_ledger_is_bounded_monotone_and_capability_bound() {
assert_eq!(super::MAX_RAW_TRANSACTION_INGEST_COVERAGE_EPOCHS, 256);
let matching = match capability(2, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 5)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("matching capability fixture unavailable"),
};
let range = match super::RawTransactionIngestCoverageRange::new(100, 200) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected coverage range failure: {error}"),
};
let epoch = match super::RawTransactionIngestCoverageEpoch::new(
1,
[2_u8; 32],
ksp_onchain_transport_lib::SolanaCommitment::Confirmed,
exact_scope("standard_logs", 5),
range,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected coverage epoch failure: {error}"),
};
let valid = super::RawTransactionIngestCoverageEpochLedger { epochs: std::vec![epoch.clone()], next_epoch_id: 2 };
assert!(valid.validate_invariants(std::slice::from_ref(&matching)).is_ok());
let requirement = super::RawTransactionIngestCoverageRequirement {
commitment: ksp_onchain_transport_lib::SolanaCommitment::Confirmed,
scope: exact_scope("standard_logs", 5),
};
let gap = match super::RawTransactionIngestGapRange::new(120, 130) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected ledger gap failure: {error}"),
};
assert_eq!(
valid.redundant_relation_for_gap([1_u8; 32], &requirement, gap),
std::option::Option::Some(super::RawTransactionIngestCoverageRelation::Exact),
);
assert!(valid.redundant_relation_for_gap([2_u8; 32], &requirement, gap).is_none());
let stale = super::RawTransactionIngestCoverageEpochLedger { epochs: std::vec![epoch.clone()], next_epoch_id: 1 };
assert!(stale.validate_invariants(std::slice::from_ref(&matching)).is_err());
let unknown = super::RawTransactionIngestCoverageEpochLedger { epochs: std::vec![epoch], next_epoch_id: 2 };
assert!(unknown.validate_invariants(&[]).is_err());
let mut too_many = std::vec::Vec::new();
for index in 0..=super::MAX_RAW_TRANSACTION_INGEST_COVERAGE_EPOCHS {
let epoch_id = (index as u64) + 1;
let epoch = match super::RawTransactionIngestCoverageEpoch::new(
epoch_id,
[2_u8; 32],
ksp_onchain_transport_lib::SolanaCommitment::Confirmed,
exact_scope("standard_logs", 5),
range,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected bounded epoch failure: {error}"),
};
too_many.push(epoch);
}
let overflow =
super::RawTransactionIngestCoverageEpochLedger { epochs: too_many, next_epoch_id: (super::MAX_RAW_TRANSACTION_INGEST_COVERAGE_EPOCHS as u64) + 2 };
assert!(overflow.validate_invariants(std::slice::from_ref(&matching)).is_err());
return;
}
#[test]
fn pre_007_known_reference_obligation_preserves_exact_reference_slot_and_commitment() {
let network = match network() {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let reference = ksp_store_lib::RawTransactionReference::new(network, ksp_store_lib::RawTransactionSignature::new([9_u8; 64]));
let obligation =
match crate::RawTransactionIngestKnownReferenceObligation::new(reference.clone(), 4242, ksp_onchain_transport_lib::SolanaCommitment::Confirmed) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected known-reference obligation failure: {error}"),
};
assert_eq!(obligation.reference(), &reference);
assert_eq!(obligation.slot(), 4242);
assert_eq!(obligation.commitment(), ksp_onchain_transport_lib::SolanaCommitment::Confirmed);
let processed = crate::RawTransactionIngestKnownReferenceObligation::new(reference, 4242, ksp_onchain_transport_lib::SolanaCommitment::Processed);
assert!(processed.is_err());
return;
}
#[test]
fn pre_008_continuity_frontier_is_gap_aware_and_recovers_after_redundant_proof() {
let first = match capability(1, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 7)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("first capability fixture unavailable"),
};
let second = match capability(2, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 7)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("second capability fixture unavailable"),
};
let mut contracts = match crate::RawTransactionIngestContinuityContracts::new(std::vec![first, second]) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("continuity contracts fixture failed: {error}"),
};
assert!(contracts.record_source_loss_gap([1_u8; 32], 100, 110).is_ok());
assert_eq!(contracts.continuity_frontier(std::option::Option::Some(120)), std::option::Option::Some(99));
assert!(contracts.record_coverage_epoch([2_u8; 32], 90, 120).is_ok());
let decision = contracts.source_loss_decision([1_u8; 32], &[[2_u8; 32]], std::option::Option::Some(120));
assert!(matches!(decision, std::result::Result::Ok(crate::RawTransactionIngestSourceLossDecision::Continue)));
assert_eq!(contracts.continuity_frontier(std::option::Option::Some(120)), std::option::Option::Some(120));
assert!(!contracts.gap_ledger.has_open_gaps());
return;
}
#[test]
fn pre_008_source_loss_requires_full_active_target_coverage() {
let first = match capability(1, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 7)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("first capability fixture unavailable"),
};
let second = match capability(2, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("helius_transaction", 8)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("second capability fixture unavailable"),
};
let mut contracts = match crate::RawTransactionIngestContinuityContracts::new(std::vec![first, second]) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("continuity contracts fixture failed: {error}"),
};
let decision = contracts.source_loss_decision([1_u8; 32], &[[2_u8; 32]], std::option::Option::Some(120));
assert!(matches!(decision, std::result::Result::Ok(crate::RawTransactionIngestSourceLossDecision::Fault)));
return;
}
#[test]
fn pre_008_open_gap_without_proven_epoch_blocks_redundant_source_continuation() {
let first = match capability(1, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 7)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("first capability fixture unavailable"),
};
let second = match capability(2, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 7)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("second capability fixture unavailable"),
};
let mut contracts = match crate::RawTransactionIngestContinuityContracts::new(std::vec![first, second]) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("continuity contracts fixture failed: {error}"),
};
let gap = match gap(1, 1, 100, 110, super::RawTransactionIngestGapState::Unresolved) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("gap fixture unavailable"),
};
contracts.gap_ledger.gaps.push(gap);
contracts.gap_ledger.next_gap_id = 2;
let decision = contracts.source_loss_decision([1_u8; 32], &[[2_u8; 32]], std::option::Option::Some(120));
assert!(matches!(decision, std::result::Result::Ok(crate::RawTransactionIngestSourceLossDecision::Fault)));
assert_eq!(contracts.continuity_frontier(std::option::Option::Some(120)), std::option::Option::Some(99));
return;
}
#[test]
fn pre_008_known_reference_missing_moves_to_continuity_ledger_and_reconciles_from_proven_full_ledger_epoch() {
let first = match capability(1, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 7)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("first capability fixture unavailable"),
};
let second =
match capability(2, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, crate::RawTransactionIngestCoverageScope::full_ledger_transactions()) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("full-ledger capability fixture unavailable"),
};
let mut contracts = match crate::RawTransactionIngestContinuityContracts::new(std::vec![first, second]) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("continuity contracts fixture failed: {error}"),
};
assert!(contracts.record_known_reference_gap([1_u8; 32], 105).is_ok());
assert_eq!(contracts.continuity_frontier(std::option::Option::Some(120)), std::option::Option::Some(104));
assert!(contracts.gap_ledger.has_open_gaps());
assert!(contracts.record_coverage_epoch([2_u8; 32], 100, 110).is_ok());
assert!(!contracts.gap_ledger.has_open_gaps());
assert_eq!(contracts.continuity_frontier(std::option::Option::Some(120)), std::option::Option::Some(120));
return;
}
#[test]
fn pre_009_health_projection_distinguishes_future_coverage_from_open_gap_reconciliation() {
let first = match capability(1, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 7)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("first capability fixture unavailable"),
};
let second = match capability(2, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 7)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("second capability fixture unavailable"),
};
let mut contracts = match crate::RawTransactionIngestContinuityContracts::new(std::vec![first, second]) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("continuity contracts fixture failed: {error}"),
};
let healthy = match contracts.health_projection(&[[1_u8; 32], [2_u8; 32]], &[], std::option::Option::Some(120)) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("health projection failed: {error}"),
};
assert_eq!(healthy, (std::option::Option::Some(120), false, true, true));
let redundant_future = match contracts.health_projection(&[[2_u8; 32]], &[[1_u8; 32]], std::option::Option::Some(120)) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("redundant health projection failed: {error}"),
};
assert_eq!(redundant_future, (std::option::Option::Some(120), false, true, false));
assert!(contracts.record_source_loss_gap([1_u8; 32], 100, 110).is_ok());
let pending = match contracts.health_projection(&[[2_u8; 32]], &[[1_u8; 32]], std::option::Option::Some(120)) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("pending health projection failed: {error}"),
};
assert_eq!(pending, (std::option::Option::Some(99), true, true, false));
assert!(contracts.record_coverage_epoch([2_u8; 32], 90, 120).is_ok());
let reconciled = match contracts.health_projection(&[[2_u8; 32]], &[[1_u8; 32]], std::option::Option::Some(120)) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("reconciled health projection failed: {error}"),
};
assert_eq!(reconciled, (std::option::Option::Some(120), false, true, true));
let uncovered_future = match contracts.health_projection(&[], &[], std::option::Option::Some(120)) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("uncovered health projection failed: {error}"),
};
assert_eq!(uncovered_future, (std::option::Option::Some(120), false, false, true));
return;
}
#[test]
fn pre_009_health_projection_rejects_duplicate_and_unknown_active_sources() {
let capability = match capability(1, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 7)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("capability fixture unavailable"),
};
let mut contracts = match crate::RawTransactionIngestContinuityContracts::new(std::vec![capability]) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("continuity contracts fixture failed: {error}"),
};
let duplicate = match contracts.health_projection(&[[1_u8; 32], [1_u8; 32]], &[], std::option::Option::Some(20)) {
std::result::Result::Ok(_) => return,
std::result::Result::Err(value) => value,
};
assert_eq!(duplicate.code(), crate::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID);
assert!(duplicate.context().iter().any(|context| return context.value() == "continuity.health_active_set_invalid"));
let unknown = match contracts.health_projection(&[[9_u8; 32]], &[], std::option::Option::Some(20)) {
std::result::Result::Ok(_) => return,
std::result::Result::Err(value) => value,
};
assert_eq!(unknown.code(), crate::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID);
assert!(unknown.context().iter().any(|context| return context.value() == "continuity.health_active_source_unknown"));
let overlap = match contracts.health_projection(&[[1_u8; 32]], &[[1_u8; 32]], std::option::Option::Some(20)) {
std::result::Result::Ok(_) => return,
std::result::Result::Err(value) => value,
};
assert_eq!(overlap.code(), crate::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID);
assert!(overlap.context().iter().any(|context| return context.value() == "continuity.health_failed_set_invalid"));
let unknown_failed = match contracts.health_projection(&[], &[[9_u8; 32]], std::option::Option::Some(20)) {
std::result::Result::Ok(_) => return,
std::result::Result::Err(value) => value,
};
assert_eq!(unknown_failed.code(), crate::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID);
assert!(unknown_failed.context().iter().any(|context| return context.value() == "continuity.health_failed_source_unknown"));
return;
}
#[test]
fn pre_011_gap_observability_projects_open_and_recent_repaired_entries() {
let first = match capability(1, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 7)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("first capability fixture unavailable"),
};
let second = match capability(2, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 7)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("second capability fixture unavailable"),
};
let mut contracts = match crate::RawTransactionIngestContinuityContracts::new(std::vec![first, second]) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("continuity contracts fixture failed: {error}"),
};
assert!(contracts.record_source_loss_gap([1_u8; 32], 100, 110).is_ok());
let pending = match contracts.observability_projection() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("pending observability projection failed: {error}"),
};
assert_eq!(pending.open_gap_count(), 1);
assert_eq!(pending.repairing_gap_count(), 0);
assert_eq!(pending.repaired_gap_total(), 0);
assert_eq!(pending.unresolved_gap_total(), 0);
assert_eq!(pending.oldest_open_gap_start_slot(), std::option::Option::Some(100));
assert_eq!(pending.gaps().len(), 1);
assert_eq!(pending.gaps()[0].start_slot(), 100);
assert_eq!(pending.gaps()[0].end_slot(), 110);
assert_eq!(pending.gaps()[0].state(), crate::RawTransactionIngestGapState::Pending);
assert_eq!(pending.gaps()[0].reason(), crate::RawTransactionIngestGapReason::SourceFailure);
assert_eq!(pending.gaps()[0].last_method(), std::option::Option::None);
assert!(contracts.record_coverage_epoch([2_u8; 32], 90, 120).is_ok());
let repaired = match contracts.observability_projection() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("repaired observability projection failed: {error}"),
};
assert_eq!(repaired.open_gap_count(), 0);
assert_eq!(repaired.repaired_gap_total(), 1);
assert_eq!(repaired.redundant_coverage_repair_total(), 1);
assert_eq!(repaired.replay_repair_total(), 0);
assert_eq!(repaired.http_scan_repair_total(), 0);
assert_eq!(repaired.repair_block_fetch_total(), 0);
assert_eq!(repaired.repair_transaction_hydration_total(), 0);
assert_eq!(repaired.oldest_open_gap_start_slot(), std::option::Option::None);
assert_eq!(repaired.gaps().len(), 1);
assert_eq!(repaired.gaps()[0].state(), crate::RawTransactionIngestGapState::Repaired);
assert_eq!(repaired.gaps()[0].last_method(), std::option::Option::Some(crate::RawTransactionIngestRepairMethod::RedundantCoverage));
return;
}
#[test]
fn pre_011_unresolved_gap_remains_visible_and_bounded() {
let capability = match capability(1, ksp_onchain_transport_lib::SolanaCommitment::Confirmed, exact_scope("standard_logs", 7)) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("capability fixture unavailable"),
};
let mut contracts = match crate::RawTransactionIngestContinuityContracts::new(std::vec![capability]) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("continuity contracts fixture failed: {error}"),
};
let unresolved = match gap(1, 1, 200, 205, super::RawTransactionIngestGapState::Unresolved) {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("unresolved gap fixture unavailable"),
};
contracts.gap_ledger.gaps.push(unresolved);
contracts.gap_ledger.next_gap_id = 2;
let projection = match contracts.observability_projection() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unresolved observability projection failed: {error}"),
};
assert_eq!(projection.open_gap_count(), 1);
assert_eq!(projection.unresolved_gap_total(), 1);
assert_eq!(projection.oldest_open_gap_start_slot(), std::option::Option::Some(200));
assert!(projection.gaps().len() <= super::MAX_RAW_TRANSACTION_INGEST_OPEN_REPAIR_GAPS);
assert_eq!(projection.gaps()[0].state(), crate::RawTransactionIngestGapState::Unresolved);
return;
}

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/snapshot.rs // file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/snapshot.rs
// version: 5 // version: 7
fn snapshot_foundation_with_source_total( fn snapshot_foundation_with_source_total(
source_total: usize, source_total: usize,
@@ -249,3 +249,100 @@ fn v0_3_13_pre_010_multi_source_counts_and_health_are_conservative_and_source_ne
assert_eq!(unhealthy.worker_snapshot().health(), ksp_worker_api::WorkerHealth::Unhealthy); assert_eq!(unhealthy.worker_snapshot().health(), ksp_worker_api::WorkerHealth::Unhealthy);
return; return;
} }
#[test]
fn v0_3_14_pre_009_health_requires_present_and_future_coverage_before_healthy() {
let (mut publisher, source) = match snapshot_foundation_with_source_total(2) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let reconnecting = crate::RawTransactionIngestProcessingFrontierProjection::new(0, std::option::Option::Some(50), std::option::Option::None)
.with_source_continuity(std::option::Option::Some(crate::RawTransactionIngestSourceState::Reconnecting), 1, 1, 1)
.with_source_counts(2, 1, 1, 0)
.with_continuity_health(std::option::Option::Some(50), false, true)
.with_failed_source_losses_reconciled(true);
assert!(publisher.record_processing_frontier(ksp_worker_api::WorkerState::Running, 0, 0, reconnecting).is_ok());
assert_eq!(source.current().worker_snapshot().health(), ksp_worker_api::WorkerHealth::Unhealthy);
let gap_pending = crate::RawTransactionIngestProcessingFrontierProjection::new(0, std::option::Option::Some(50), std::option::Option::None)
.with_source_continuity(std::option::Option::Some(crate::RawTransactionIngestSourceState::Active), 1, 1, 1)
.with_source_counts(2, 2, 0, 0)
.with_continuity_health(std::option::Option::Some(49), true, true)
.with_failed_source_losses_reconciled(true);
assert!(publisher.record_processing_frontier(ksp_worker_api::WorkerState::Running, 0, 0, gap_pending).is_ok());
assert_eq!(source.current().worker_snapshot().health(), ksp_worker_api::WorkerHealth::Unhealthy);
let healthy = crate::RawTransactionIngestProcessingFrontierProjection::new(0, std::option::Option::Some(50), std::option::Option::None)
.with_source_continuity(std::option::Option::Some(crate::RawTransactionIngestSourceState::Active), 1, 1, 1)
.with_source_counts(2, 2, 0, 0)
.with_continuity_health(std::option::Option::Some(50), false, true)
.with_failed_source_losses_reconciled(true);
assert!(publisher.record_processing_frontier(ksp_worker_api::WorkerState::Running, 0, 0, healthy).is_ok());
assert_eq!(source.current().worker_snapshot().health(), ksp_worker_api::WorkerHealth::Healthy);
let failed_unreconciled = crate::RawTransactionIngestProcessingFrontierProjection::new(0, std::option::Option::Some(50), std::option::Option::None)
.with_source_continuity(std::option::Option::Some(crate::RawTransactionIngestSourceState::Failed), 1, 1, 1)
.with_source_counts(2, 1, 0, 1)
.with_continuity_health(std::option::Option::Some(50), false, true)
.with_failed_source_losses_reconciled(false);
assert!(publisher.record_processing_frontier(ksp_worker_api::WorkerState::Running, 0, 0, failed_unreconciled).is_ok());
assert_eq!(source.current().worker_snapshot().health(), ksp_worker_api::WorkerHealth::Unhealthy);
let degraded = crate::RawTransactionIngestProcessingFrontierProjection::new(0, std::option::Option::Some(50), std::option::Option::None)
.with_source_continuity(std::option::Option::Some(crate::RawTransactionIngestSourceState::Failed), 1, 1, 1)
.with_source_counts(2, 1, 0, 1)
.with_continuity_health(std::option::Option::Some(50), false, true)
.with_failed_source_losses_reconciled(true);
assert!(publisher.record_processing_frontier(ksp_worker_api::WorkerState::Running, 0, 0, degraded).is_ok());
assert_eq!(source.current().worker_snapshot().health(), ksp_worker_api::WorkerHealth::Degraded);
let future_uncovered = crate::RawTransactionIngestProcessingFrontierProjection::new(0, std::option::Option::Some(50), std::option::Option::None)
.with_source_continuity(std::option::Option::Some(crate::RawTransactionIngestSourceState::Failed), 1, 1, 1)
.with_source_counts(2, 1, 0, 1)
.with_continuity_health(std::option::Option::Some(50), false, false)
.with_failed_source_losses_reconciled(true);
assert!(publisher.record_processing_frontier(ksp_worker_api::WorkerState::Running, 0, 0, future_uncovered).is_ok());
assert_eq!(source.current().worker_snapshot().health(), ksp_worker_api::WorkerHealth::Unhealthy);
assert!(publisher.publish_state(ksp_worker_api::WorkerState::Faulted(crate::ERROR_CODE_RAW_TRANSACTION_INGEST_SOURCE_FAILED), 0, 0).is_ok());
let faulted = source.current();
assert!(matches!(faulted.worker_snapshot().state(), ksp_worker_api::WorkerState::Faulted(_)));
assert_eq!(faulted.worker_snapshot().health(), ksp_worker_api::WorkerHealth::Unhealthy);
return;
}
#[test]
fn v0_3_14_pre_011_snapshot_carries_checked_gap_observability() {
let (mut publisher, source) = match snapshot_foundation_with_source_total(2) {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let gap = crate::RawTransactionIngestGapSnapshot::new(
crate::RawTransactionIngestGapId::new(7),
100,
110,
crate::RawTransactionIngestGapState::Pending,
crate::RawTransactionIngestGapReason::SourceFailure,
std::option::Option::None,
);
let continuity_snapshot = crate::RawTransactionIngestContinuitySnapshotProjection::empty()
.with_gap_state(std::vec![gap], 1, 0, std::option::Option::Some(100))
.with_recovery_totals(3, 1, 2, 1, 0)
.with_material_totals(4, 5);
let projection = crate::RawTransactionIngestProcessingFrontierProjection::new(0, std::option::Option::Some(120), std::option::Option::None)
.with_continuity_snapshot(continuity_snapshot);
assert!(publisher.record_processing_frontier(ksp_worker_api::WorkerState::Running, 0, 0, projection).is_ok());
let snapshot = source.current();
assert_eq!(snapshot.gaps().len(), 1);
assert_eq!(snapshot.gaps()[0].gap_id().value(), 7);
assert_eq!(snapshot.gaps()[0].start_slot(), 100);
assert_eq!(snapshot.gaps()[0].end_slot(), 110);
assert_eq!(snapshot.gaps()[0].state(), crate::RawTransactionIngestGapState::Pending);
assert_eq!(snapshot.gaps()[0].reason(), crate::RawTransactionIngestGapReason::SourceFailure);
assert_eq!(snapshot.gaps()[0].last_method(), std::option::Option::None);
assert_eq!(snapshot.open_gap_count(), 1);
assert_eq!(snapshot.repairing_gap_count(), 0);
assert_eq!(snapshot.repaired_gap_total(), 3);
assert_eq!(snapshot.unresolved_gap_total(), 1);
assert_eq!(snapshot.replay_repair_total(), 2);
assert_eq!(snapshot.redundant_coverage_repair_total(), 1);
assert_eq!(snapshot.http_scan_repair_total(), 0);
assert_eq!(snapshot.repair_block_fetch_total(), 4);
assert_eq!(snapshot.repair_transaction_hydration_total(), 5);
assert_eq!(snapshot.oldest_open_gap_start_slot(), std::option::Option::Some(100));
return;
}

218
deltas/0.3.14/pre.001.md Normal file
View File

@@ -0,0 +1,218 @@
<!-- file: deltas/0.3.14/pre.001.md -->
<!-- version: 5 -->
# Delta `0.3.14-pre.001` — audit / brainstorming / sizing gap repair
## Base requise
```text
v0.3.13
workspace.package.version = 0.3.13
deltas/0.3.13/rel.001.md présent
```
Archive opérateur auditée :
```text
khadhroony-solana-project-v0.3.13.zip
SHA-256 253fa7ef5e0a6b2721da66fc3ea8dc7695d991d3a00bcc7d34396d9a32b65245
```
L'archive ne contient pas `.git`; l'identité interne stable est contrôlée mais l'objet du tag Git ne peut pas être comparé localement.
## Objet
Exécuter le gate `pre.001` imposé par `prompts/033-V0_3_14_START_PROMPT.md` avant tout codage de repair :
```text
lecture complète des règles et du handoff 0.3.13
audit archive/base stable
audit Worker/Transport/frontier/replay/continuity
audit externe courant Solana/Yellowstone/providers
matrice des cinq familles live
modèle gap/range/coverage run-local
sémantique repaired/unresolved
health candidate
threat model
smokes accessibles
Cargo/features cible
sizing et décision maintien/rescission
plan 035 + validation 031
```
Aucun moteur de repair, failover/degraded runtime, modification Transport/Config ou adapter EARLY n'est implémenté.
## Décisions
```text
reconnect != replay_attempt != replay_covered != repaired
replay accepté ne prouve pas la coverage
transaction-only source ne prouve pas l'absence d'événements manquants
repair final qualifié au niveau slot/range
skipped seulement après discovery slots réussie
getBlock null sur slot produit reste unresolved
getTransaction null n'est pas une preuve d'inexistence
Common RAW/admission/ksp-store-lib restent le chemin unique
retry/reconnect restent Transport-owned
required/redundant est une relation de coverage dynamique, pas un flag provider
processing frontier != continuity frontier
pool HTTP d'hydration != capability de scan : getTransaction seul est garanti pour Yellowstone/Logs/Helius
les façades WS doivent exposer une observation latest-value de leur snapshot avant le repair ; aucun nouveau socket/actor
range gap inclusif pour ne pas perdre la fin d'un slot déjà partiellement observé ; aucun slot dérivé d'un timestamp
TargetCoverage = réunion conservative des scopes configurés ; source perdue non respawnée par Worker
FullLedgerTransactions et ExactSourceScope sont distingués ; aucune équivalence cross-family implicite
getBlocksWithLimit ne ferme une plage que si la fenêtre cible est explicitement prouvée complète
scan full-block interdit s'il élargirait silencieusement un run uniquement filtré
EARLY hors scope 0.3.14
aucune nouvelle dépendance
Config inchangé en pre.001 ; modification future seulement sur besoin démontré
0.3.14 maintenue sans rescission sous le scope des cinq familles existantes
```
Forecast recalibré : `pre.002 -> pre.016`, avec une tranche WS continuity dédiée, replay Yellowstone et coverage redondante séparés, puis HTTP/hydration/reconciliation/health/fairness/observability/races/gates/docs/publication. Cette extension remplace le forecast initial `pre.002 -> pre.014` afin de respecter les slices 1520 minutes.
Bornes cibles à matérialiser dans les tranches techniques :
```text
MAX_OPEN_REPAIR_GAPS = 64
MAX_REPAIR_RANGE_SLOTS = 4096
MAX_REPAIR_DISCOVERY_WINDOW_SLOTS = 512
MAX_REPAIR_BLOCK_FETCH_IN_FLIGHT = 4
MAX_REPAIR_ACTIVE_GAPS = 1
```
## Audit externe — points structurants
Le proto Yellowstone utilisé expose `from_slot` et `SubscribeReplayInfo/first_available`. `yellowstone-grpc-proto 12.7.0` est courant au moment de l'audit.
Le changelog upstream du 22 juillet 2026 documente un bug corrigé où un replay `blocks` pouvait accepter `from_slot`, ne rejouer aucun block, puis reprendre au head live avec un gap. La preuve KSP ne peut donc pas être « request accepted ».
PublicNode expose actuellement Solana Mainnet/Testnet RPC/WS/Yellowstone et archive data, mais sa profondeur de replay/first_available n'est pas suffisamment documentée pour être revendiquée.
OrbitFlare expose Yellowstone et Devnet gRPC sur ses tiers courants ; un exemple récent utilise `from_slot`, mais la profondeur/first_available de l'endpoint KSP reste à prouver par smoke.
Helius documente LaserStream gRPC avec replay jusqu'à 24 h, mais cette surface est distincte du `transactionSubscribe` WSS stable et n'est pas ajoutée à `0.3.14`.
QuickNode documente un `fromSlot` provider-specific jusqu'à 3000 slots ; cette comparaison confirme qu'une profondeur de replay ne peut pas être généralisée entre providers.
## Fichiers ajoutés
```text
docs/plans/035-V0_3_14_MULTI_SOURCE_GAP_REPAIR_HARDENING_PLAN.md
docs/validation/031-V0_3_14_MULTI_SOURCE_GAP_REPAIR_HARDENING.md
deltas/0.3.14/pre.001.md
```
## Fichiers modifiés
```text
Cargo.toml
```
## Fichiers supprimés
```text
aucun
```
## Version Cargo
```text
header : 565 -> 566
workspace.package.version : 0.3.13 -> 0.3.14-pre.1
```
Aucune dépendance, feature ou autre ligne fonctionnelle du manifest racine n'est changée.
## Surfaces explicitement inchangées
```text
crates/**
config/**
.env.example
README.md
RULES.md
ROADMAP.md
CHANGELOG.md
docs/architecture/**
prompts/**
deltas/0.3.13/**
```
## Validations base exécutées
Localement avant modification :
```text
unzip -t : PASS
archive safety : PASS
workspace/crate inventory : 21/21
General Rust rule audit : clean
Rust export completeness audit : 0 candidate(s)
KSP workspace Rust rule audit : clean
Markdown table audit : clean (340 tables / 855 files)
489 définitions de règles / 489 IDs uniques / 0 doublon
```
Le journal opérateur fourni pour la stable contient :
```text
cargo fmt/check : terminé sans erreur visible
cargo check --workspace : terminé sans erreur visible
cargo clippy --workspace --all-targets --all-features -- -D warnings : terminé sans erreur visible
cargo test --workspace --all-targets --all-features : 1 850 PASS / 0 failed / 15 ignored
cargo tree Worker direct/features : produit
cargo tree --duplicates : produit
```
Cette preuve concerne `0.3.13`, pas le bump `0.3.14-pre.1`.
## Validations post-modification
```text
General Rust rule audit : clean
Rust export completeness audit : 0 candidate(s)
KSP workspace Rust rule audit : clean
Markdown table audit : clean (340 tables / 858 files)
rule definitions : 489 / 489 uniques / 0 doublon
comparaison stable -> worktree : 3 ajouts / 1 modification / 0 suppression
Cargo fmt/check : NON EXÉCUTÉ LOCAL — cargo absent du sandbox
```
Le `scripts/__pycache__` recréé par les audits Python est supprimé avant packaging et ne fait pas partie de la livraison.
Archive de livraison attendue :
```text
ksp-general-0.3.14-pre.001.zip
```
## Archive déchange
Conformément à `VER-ARCHIVE-001`, `VER-ARCHIVE-004` et `VER-ARCHIVE-005`, la livraison est une archive `general` minimale :
```text
ksp-general-0.3.14-pre.001.zip
```
Contenu attendu, exactement quatre fichiers :
```text
Cargo.toml
docs/plans/035-V0_3_14_MULTI_SOURCE_GAP_REPAIR_HARDENING_PLAN.md
docs/validation/031-V0_3_14_MULTI_SOURCE_GAP_REPAIR_HARDENING.md
deltas/0.3.14/pre.001.md
```
Aucun fichier inchangé de la stable, cache, lockfile, secret, sortie de compilation ou artefact daudit nest inclus.
## Gate opérateur après application
```bash
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/0.3.14
cargo check --workspace
```
Si ce gate est propre, `pre.002` peut matérialiser uniquement les contrats gap/range/coverage prévus par le plan `035`.

View File

@@ -0,0 +1,203 @@
<!-- file: deltas/0.3.14/pre.002-fix.001.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.002-fix.001` — correction frontières replay et dead-code continuity
## Base requise
```text
0.3.14-pre.002
workspace.package.version = 0.3.14-pre.2
deltas/0.3.14/pre.002.md présent
```
## Objectif
Corriger strictement la tranche `pre.002` après le gate opérateur, sans étendre son périmètre fonctionnel :
```text
supprimer la mutation Worker du cursor Yellowstone from_slot
préserver la propriété Transport-owned replay
supprimer le dead_code de RawTransactionIngestGapRange::new sans suppression de validation ni lint bypass
conserver pre.002 sans repair I/O
```
## Défauts observés
Le gate opérateur sur `0.3.14-pre.2` a produit :
```text
cargo check --workspace : PASS avec 1 warning dead_code
cargo clippy --workspace --all-targets --all-features -- -D warnings : FAIL
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features : FAIL
```
Clippy :
```text
associated function `new` is never used
crates/ksp-worker-raw-transaction-ingest-lib/src/continuity.rs:102
-D dead-code implied by -D warnings
```
Test d'intégration :
```text
v0_3_12_pre_008_worker_observes_transport_reconnect_replay_and_faults_only_on_proven_retention_gap : FAIL
Worker took ownership of replay/repair responsibility: set_from_slot(
```
Les 114 unit tests du crate passaient avant l'échec du test de frontière.
## Cause
`pre.002` construisait le fingerprint de coverage Yellowstone en clonant la requête puis en supprimant :
```text
commitment
from_slot
ping
```
La suppression de `from_slot` appelait `YellowstoneSubscribeRequest::set_from_slot(None)` depuis `runtime_resources.rs`. Même sans I/O, cette mutation viole la frontière durable établie en `0.3.12-pre.008` : le Worker observe le replay Yellowstone mais ne possède ni ne modifie son cursor ; la responsabilité `from_slot` reste dans Transport.
Le constructeur privé `RawTransactionIngestGapRange::new` était par ailleurs appelé uniquement depuis les unit tests de `pre.002`, donc considéré mort dans la compilation normale de la bibliothèque.
## Correction
### Yellowstone coverage fingerprint
Le Worker continue de neutraliser uniquement les éléments communs qu'il peut traiter sans absorber la responsabilité replay :
```text
commitment -> retiré du fingerprint opaque, car porté séparément par TargetCoverage
ping -> retiré du fingerprint opaque, car non métier
from_slot -> conservé dans l'identité opaque Transport
```
Le Worker n'appelle plus `set_from_slot`.
Conserver `from_slot` dans l'identité rend la comparaison de deux scopes Yellowstone volontairement plus conservative : deux requêtes identiques métier mais portant des cursors replay différents peuvent rester distinctes. Cette différence ne peut pas broaden la coverage ni produire une fausse preuve de complétude. Une éventuelle identité Transport spécifiquement dédiée au scope métier relève d'une tranche Transport ultérieure, pas de ce fix.
Cette correction remplace donc la décision trop agressive de `pre.002` qui annonçait l'exclusion du cursor replay du fingerprint Worker.
### Gap range validation
`RawTransactionIngestGapLedger::validate_invariants()` réutilise maintenant `RawTransactionIngestGapRange::new(start_slot, end_slot)` pour revalider chaque plage retenue.
Cela :
```text
rend le constructeur vivant dans le code de production
conserve le rejet reversed
conserve le contrôle checked du span inclusif
ajoute au recheck du ledger la borne MAX_RAW_TRANSACTION_INGEST_REPAIR_RANGE_SLOTS
n'introduit aucun allow/expect dead_code
```
## Fichiers ajoutés
```text
deltas/0.3.14/pre.002-fix.001.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/continuity.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
```
## Fichiers supprimés
```text
aucun
```
## Version Cargo
Le fix modifie du code Rust ; conformément à `VER-ID-007` et `VER-ID-010` :
```text
header Cargo.toml : 567 -> 568
workspace.package.version : 0.3.14-pre.2 -> 0.3.14-pre.2.fix.1
```
Versions de fichiers :
```text
continuity.rs : 1 -> 2
runtime_resources.rs : 27 -> 28
```
## Frontières préservées
```text
aucun repair I/O
aucune nouvelle tâche
aucune nouvelle socket
aucun retry Worker
aucune nouvelle dépendance
aucune nouvelle feature
aucun accès Config depuis Worker
aucun accès Job Backfill depuis Worker
aucun backend Store physique depuis Worker
aucune mutation Worker de Yellowstone from_slot
```
## Validations exécutées
Dans le sandbox de préparation :
```text
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/0.3.14
scan statique de la frontière set_from_slot dans runtime_resources.rs
scan du constructeur RawTransactionIngestGapRange::new dans le chemin production
comparaison exacte pre.002 -> pre.002-fix.001
contrôle contenu archive delta
unzip -t archive delta
```
## Validations non exécutées
Le sandbox de préparation ne fournit pas le toolchain Cargo/Rust. Les gates suivants restent à exécuter côté 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-worker-raw-transaction-ingest-lib --all-targets --all-features
```
Le test de frontière existant qui a détecté la régression n'est ni modifié ni assoupli.
## Décisions prises
```text
fix strict de pre.002, sans nouvelle responsabilité fonctionnelle
Transport reste propriétaire de from_slot/replay
le Worker privilégie une identité Yellowstone conservative plutôt qu'une normalisation interdite
le ledger revalide ses ranges via le constructeur canonique
aucun lint bypass n'est accepté pour dead_code
```
## Questions ouvertes
```text
aucune pour ce fix
```
## Gate opérateur après application
```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-worker-raw-transaction-ingest-lib --all-targets --all-features
```

View File

@@ -0,0 +1,220 @@
<!-- file: deltas/0.3.14/pre.002-fix.002.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.002-fix.002` — correction façade crate-root continuity
## Base requise
```text
0.3.14-pre.002-fix.001
workspace.package.version = 0.3.14-pre.2.fix.1
deltas/0.3.14/pre.002-fix.001.md présent
```
## Objectif
Corriger strictement la tranche `pre.002` après le second gate opérateur, sans étendre son périmètre fonctionnel :
```text
préserver le canari historique pre.007 interdisant le vocabulaire repair dans la crate-root
conserver les contrats continuity/gap de pre.002 entièrement privés
ne réexporter au crate-root que les éléments pub(crate) réellement partagés entre modules
conserver pre.002 sans repair I/O
```
## Défaut observé
Le gate opérateur sur `0.3.14-pre.2.fix.1` a produit :
```text
cargo fmt --all : PASS
cargo fmt --all -- --check : PASS
audit Rust workspace rules : PASS
audit Markdown tables : PASS
cargo check --workspace : PASS
cargo clippy --workspace --all-targets --all-features -- -D warnings : PASS
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features : FAIL
```
Les `114` unit tests passaient, ainsi que les suites `cross_layer_completeness`, `dependency_boundary` et `hardening` exécutées avant l'échec.
Échec :
```text
v0_3_12_pre_007_processing_frontier_snapshot_getters_are_public_and_processing_only : FAIL
pre.007 public root overclaims continuity/replay: repair
```
Le test historique vérifie directement `src/lib.rs` et interdit les tokens suivants :
```text
ReplayInfo
from_slot
repair
backfill
```
## Cause
`pre.002` avait correctement gardé ses nouveaux contrats en `pub(crate)`, mais avait réexporté au crate-root plusieurs bornes internes nommées `*_REPAIR_*` ainsi que `RawTransactionIngestRepairCapabilityDescriptor`.
Ces éléments n'étaient pas publics hors crate, mais leur présence textuelle dans `src/lib.rs` violait malgré tout le contrat historique `pre.007` : la façade crate-root ne doit pas revendiquer une responsabilité de replay/repair.
Cinq bornes étaient en outre utilisées uniquement dans `continuity.rs` et son module de unit tests ; elles n'avaient donc pas besoin d'être `pub(crate)` ni réexportées.
## Correction
### Bornes de continuity
Les cinq bornes restent inchangées en valeur et en sémantique, mais deviennent strictement privées au module `continuity` :
```text
MAX_RAW_TRANSACTION_INGEST_OPEN_REPAIR_GAPS = 64
MAX_RAW_TRANSACTION_INGEST_REPAIR_ACTIVE_GAPS = 1
MAX_RAW_TRANSACTION_INGEST_REPAIR_BLOCK_FETCH_IN_FLIGHT = 4
MAX_RAW_TRANSACTION_INGEST_REPAIR_DISCOVERY_WINDOW_SLOTS = 512
MAX_RAW_TRANSACTION_INGEST_REPAIR_RANGE_SLOTS = 4096
```
Leurs réexports `pub(crate)` sont supprimés de `src/lib.rs`.
Les unit tests rattachés au module parent y accèdent maintenant via `super::...`, conformément à la règle de visibilité des éléments strictement privés.
### Capability descriptor partagé
Le seul descriptor réellement partagé entre `continuity.rs` et `runtime_resources.rs` reste `pub(crate)` et réexporté au crate-root, mais sous un nom source-neutre :
```text
RawTransactionIngestRepairCapabilityDescriptor
-> RawTransactionIngestContinuityCapabilityDescriptor
```
Sa structure, ses validations et ses données ne changent pas.
Le helper privé `repair_capability_descriptor` de `runtime_resources.rs` n'est pas une façade crate-root et conserve son rôle actuel ; aucun repair I/O n'est ajouté.
### Canari de non-régression
Le test historique `tests/public_api.rs` n'est ni modifié ni assoupli.
Le canari `v0_3_14_pre_002_continuity_contracts_are_private_bounded_and_io_free` est renforcé afin d'exiger lui aussi :
```text
src/lib.rs ne contient pas "repair"
```
## Fichiers ajoutés
```text
deltas/0.3.14/pre.002-fix.002.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/continuity.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/continuity.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
```
## Fichiers supprimés
```text
aucun
```
## Version Cargo
Le fix modifie du code Rust ; conformément à `VER-ID-007` et `VER-ID-010` :
```text
header Cargo.toml : 568 -> 569
workspace.package.version : 0.3.14-pre.2.fix.1 -> 0.3.14-pre.2.fix.2
```
Versions de fichiers :
```text
src/lib.rs : 27 -> 28
src/continuity.rs : 2 -> 3
src/runtime_resources.rs : 28 -> 29
unit_tests/continuity.rs : 1 -> 2
tests/hardening.rs : 25 -> 26
```
## Frontières préservées
```text
aucun repair I/O
aucune nouvelle tâche
aucune nouvelle socket
aucun retry Worker
aucune nouvelle dépendance
aucune nouvelle feature
aucun accès Config depuis Worker
aucun accès Job Backfill depuis Worker
aucun backend Store physique depuis Worker
aucune mutation Worker de Yellowstone from_slot
aucun nouveau symbole public
crate-root toujours exempte de ReplayInfo/from_slot/repair/backfill
```
## Validations exécutées
Dans le sandbox de préparation :
```text
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
scan exact des tokens ReplayInfo/from_slot/repair/backfill dans src/lib.rs
scan des références vers l'ancien RawTransactionIngestRepairCapabilityDescriptor
scan des références des bornes privées de continuity
comparaison exacte pre.002-fix.001 -> pre.002-fix.002
contrôle contenu archive delta
unzip -t archive delta
```
## Validations non exécutées
Le sandbox de préparation ne fournit pas le toolchain Cargo/Rust. Les gates suivants restent à exécuter côté 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-worker-raw-transaction-ingest-lib --all-targets --all-features
```
Le test historique `v0_3_12_pre_007_processing_frontier_snapshot_getters_are_public_and_processing_only` n'est pas modifié.
## Décisions prises
```text
fix strict de pre.002, sans nouvelle responsabilité fonctionnelle
les bornes non partagées restent privées à continuity.rs
seuls les éléments réellement partagés sont pub(crate) et réexportés au crate-root
le descriptor partagé porte une identité continuity source-neutre
aucun contournement du canari historique n'est accepté
```
## Questions ouvertes
```text
aucune pour ce fix
```
## Gate opérateur après application
```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-worker-raw-transaction-ingest-lib --all-targets --all-features
```

257
deltas/0.3.14/pre.002.md Normal file
View File

@@ -0,0 +1,257 @@
<!-- file: deltas/0.3.14/pre.002.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.002` — contrats run-local gap / range / coverage
## Base requise
```text
0.3.14-pre.001
workspace.package.version = 0.3.14-pre.1
deltas/0.3.14/pre.001.md présent
```
Gate opérateur communiqué sur `0.3.14-pre.1` :
```text
cargo fmt --all -- --check : PASS
General Rust rule audit : clean
Rust export completeness audit : 0 candidate(s)
KSP workspace Rust rule audit : clean
Markdown table audit : clean (306 tables / 181 files sur le périmètre demandé)
cargo check --workspace : PASS
```
## Objectif
Matérialiser uniquement la tranche `pre.002` du plan `035` :
```text
contrats run-local de gap et plage inclusive
TargetCoverage conservative par commitment et scope
FullLedgerTransactions / ExactSourceScope / KnownReferences
inventaire privé des capabilities de repair déjà composées
ledger run-local borné et invariants de coalescence
bornes de future discovery / fetch / active repair
raccord pur aux RuntimeResources avant spawn des sources
```
Cette tranche ne réalise aucun repair, replay, scan HTTP, hydration de gap ou I/O Store supplémentaire.
## Fichiers ajoutés
```text
crates/ksp-worker-raw-transaction-ingest-lib/src/continuity.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/continuity.rs
deltas/0.3.14/pre.002.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
```
## Fichiers supprimés
```text
aucun
```
## Contrats introduits
Bornes run-local :
```text
MAX_RAW_TRANSACTION_INGEST_OPEN_REPAIR_GAPS = 64
MAX_RAW_TRANSACTION_INGEST_REPAIR_RANGE_SLOTS = 4096
MAX_RAW_TRANSACTION_INGEST_REPAIR_DISCOVERY_WINDOW_SLOTS = 512
MAX_RAW_TRANSACTION_INGEST_REPAIR_BLOCK_FETCH_IN_FLIGHT = 4
MAX_RAW_TRANSACTION_INGEST_REPAIR_ACTIVE_GAPS = 1
```
Le scope de coverage distingue explicitement :
```text
FullLedgerTransactions
ExactSourceScope(family, semantic_fingerprint)
KnownReferences(fingerprint)
```
`KnownReferences` est admis comme notion de capability mais rejeté comme `TargetCoverage` configurée : connaître des signatures à hydrater ne prouve jamais qu'aucune transaction n'a été perdue.
Les plages de gap sont inclusives, bornées, validées avec arithmétique checked et coalescibles seulement lorsqu'elles se chevauchent ou sont adjacentes sans dépasser la borne maximale.
Le ledger impose notamment :
```text
réseau unique du run
GapId non nul, unique et monotone
maximum 64 gaps ouverts
maximum 1 gap en Repairing
pas de ranges ouverts coalescibles du même source/scope/commitment/reason
aucun wrap implicite des compteurs ou bornes
```
## Capabilities source
L'inventaire est dérivé des `RuntimeResources` déjà validées, sans I/O :
```text
HTTP Block Polling All -> FullLedgerTransactions
Standard Block All -> FullLedgerTransactions
Standard Block filtered -> ExactSourceScope
Standard Logs -> ExactSourceScope
Helius transactionSubscribe -> ExactSourceScope
Yellowstone -> ExactSourceScope provider-neutral
```
La capability de scan HTTP n'est pas inférée de la simple présence d'un `HttpTransportPool`. Elle est déclarée seulement lorsque le rôle fourni expose réellement, sur le réseau attendu :
```text
getBlock
getSlot
et au moins getBlocks ou getBlocksWithLimit
```
La disponibilité de `getTransaction` est détectée séparément pour la future hydration de références.
Pour Yellowstone, le fingerprint de coverage retire du calcul les éléments de transport/continuité qui ne définissent pas le scope métier :
```text
commitment
from_slot
ping
```
Le scope reste volontairement conservatif : aucune équivalence cross-family ou provider n'est inventée.
## Intégration runtime
Avant le spawn des sources live, `RawTransactionIngestRuntimeResources` :
```text
construit exactement un descriptor par source
valide réseau / source keys / TargetCoverage / bornes
construit un ledger vide run-local
conserve ces contrats pendant la durée du supervisor
revalide les invariants à la fermeture
```
Aucune nouvelle tâche, socket, retry, requête HTTP/gRPC/WS, lecture Config ou opération Store n'est ajoutée par cette tranche.
## Tests ajoutés / renforcés
Les tests couvrent :
```text
valeurs exactes des cinq bornes
plages inclusives / reversed / maximum / overflow
chevauchement et adjacency
merge borné
TargetCoverage exact dédupliquée
absence de broadening entre commitments
subsomption FullLedger seulement au même commitment
rejet de KnownReferences comme target
limites open/active gaps
monotonicité GapId
module production continuity privé
absence d'I/O et de nouvelles frontières dans continuity.rs
inventaire de modules de production mis à jour
```
La surface publique du crate reste inchangée ; les nouveaux contrats sont `pub(crate)` et réexportés uniquement par la façade crate-root pour usage interne conforme aux règles KSP.
## Dépendances / features
```text
aucune nouvelle dépendance
aucune nouvelle feature
aucun SDK provider
aucun accès Config depuis le Worker
aucun accès Job Backfill depuis le Worker
aucun backend Store physique depuis le Worker
```
## Version Cargo
```text
header Cargo.toml : 566 -> 567
workspace.package.version : 0.3.14-pre.1 -> 0.3.14-pre.2
```
## Validations exécutées
Dans le sandbox de préparation :
```text
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/0.3.14
scan statique frontières / I/O continuity
comparaison exacte avec la base pre.001
contrôle contenu archive delta
unzip -t archive delta
```
Résultats :
```text
General Rust rule audit : clean
Rust export completeness audit : 0 candidate(s)
KSP workspace Rust rule audit : clean
Markdown table audit : clean (306 tables / 182 files)
scan continuity I/O / frontières : PASS
scan surface publique continuity : PASS
comparaison pre.001 -> pre.002 : 3 ajouts / 5 modifications / 0 suppression
```
Le contrôle ZIP est exécuté après construction de l'archive de livraison ; toute archive qui échoue ce contrôle est rejetée et n'est pas livrée.
## Validations non exécutées
Le sandbox de préparation ne fournit pas le toolchain Cargo/Rust ; les gates suivants ne peuvent donc pas constituer une preuve locale :
```text
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features
```
Ils doivent être exécutés côté opérateur après application du delta.
## Décisions prises
```text
pre.002 reste sans repair I/O
TargetCoverage est conservative et commitment-sensitive
FullLedger ne broaden jamais un commitment différent
KnownReferences ne prouve jamais la complétude
HTTP repair capability est détectée par méthodes réellement routables
Yellowstone coverage identity exclut cursor replay et identité provider/endpoint
les contrats restent privés au Worker
aucune nouvelle dépendance ou frontière architecturale
```
## Questions ouvertes
```text
aucune pour pre.002
```
La tranche suivante reste `pre.003` : observabilité latest-value de continuité WebSocket, sans encore déclencher le repair.
## Gate opérateur après application
```bash
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/0.3.14
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features
```

333
deltas/0.3.14/pre.003.md Normal file
View File

@@ -0,0 +1,333 @@
<!-- file: deltas/0.3.14/pre.003.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.003` — observabilité latest-value de continuité WebSocket
## Base requise
```text
0.3.14-pre.002-fix.002
workspace.package.version = 0.3.14-pre.2.fix.2
deltas/0.3.14/pre.002-fix.002.md présent
```
## Gate de la base
Le gate opérateur de `0.3.14-pre.002-fix.002` est validé avant ouverture de cette tranche :
```text
cargo fmt --all : PASS
cargo fmt --all -- --check : PASS
audit Rust workspace rules : PASS
audit Markdown tables : PASS
cargo check --workspace : PASS
cargo clippy --workspace --all-targets --all-features -- -D warnings : PASS
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features : PASS
```
Le gate Worker comprend notamment :
```text
114 unit tests : PASS
cross_layer_completeness : 8 PASS
dependency_boundary : 19 PASS
hardening : 27 PASS
public_api : 20 PASS
release_completeness : 5 PASS
```
## Objectif
Implémenter strictement la tranche `pre.003` du plan `035` :
```text
ajouter dans Transport une source latest-value sûre pour les snapshots WebSocket typés
la déléguer depuis les façades Standard et Helius existantes
observer les incidents reconnect/overflow dans les sources Standard Logs, Standard Block et Helius du Worker
ancrer un incident sur le dernier slot réellement observé par cette source
borner l'incident avec le premier slot post-incident réellement reçu
conserver une terminalité conservative tant que les tranches de replay/repair ultérieures ne sont pas présentes
ne créer aucun nouveau socket, actor, retry ou repair HTTP
```
Cette tranche ne remplace pas encore un gap WebSocket par une réparation. Elle rend l'incident explicitement observable et bornable en réutilisant exclusivement les snapshots déjà publiés par l'actor Transport existant.
## Transport — source latest-value WebSocket
`WsSession` possède déjà un `tokio::sync::watch::Receiver<WsSessionSnapshot>` interne utilisé pour `snapshot()` et `state()`.
`pre.003` ajoute `WsSessionSnapshotSource`, un observateur cloneable qui encapsule uniquement un clone de ce receiver et expose :
```text
current() -> dernier WsSessionSnapshot sûr
wait_for_change() -> prochaine valeur latest-value, ou None si l'actor disparaît
```
Le type ne contient ni URL, ni credentials, ni socket, ni payload de notification. Son `Debug` reste fondé sur la projection sûre existante.
`WsSession::snapshot_source()` clone le receiver existant. Les façades :
```text
SolanaStandardWsSession
HeliusLaserStreamWsSession
```
délèguent cette méthode au même `WsSession` physique. Aucun second runtime WebSocket n'est créé.
## Worker — ancre d'incident WebSocket
Le Worker introduit un contrat crate-private `RawTransactionIngestWebSocketIncidentAnchor`.
Il conserve uniquement :
```text
start_slot
end_slot optionnel
compteurs de continuity gap / overflow observés au moment de l'incident
motifs reconnect et/ou overflow
```
La borne de début respecte le plan `035` :
```text
start_slot = dernier slot réellement observé par la source avant l'incident
```
Aucun timestamp, frontier dérivé d'une autre source ou `last_slot + 1` n'est utilisé comme preuve de début.
La borne de fin est :
```text
end_slot = premier slot réellement reçu par la même source après l'incident
```
Le range reste inclusif. Les validations réutilisent les bornes run-local introduites en `pre.002` et rejettent notamment inversion, dépassement de plage et compteur régressif.
Si un reconnect/overflow est observé avant qu'un slot source n'ait été vu, le Worker ne fabrique pas d'historique : l'incident est `source.websocket_incident_unbounded`.
## Worker — observation des snapshots
`RawTransactionIngestProcessingFrontierReporter` observe maintenant les `WsSessionSnapshot` Transport et maintient :
```text
continuity gap total WebSocket
notification overflow total
ancre d'incident WebSocket éventuelle
projection de lifecycle source
```
Une source redevenue `Active` reste projetée `Reconnecting` tant qu'un incident ouvert n'a pas reçu sa borne post-incident. Cela évite de publier transitoirement une continuité saine alors que le gap n'est pas encore borné.
Aucune mutation de la politique de reconnect Transport n'est effectuée par le Worker.
## Standard Logs et Helius transaction
Les deux sources réutilisent maintenant `session.snapshot_source()` en parallèle de la réception métier et des tâches d'hydration existantes.
Lorsqu'un incident est détecté :
```text
le premier signal post-incident borne le range de manière inclusive
ce signal est quand même admis dans le coordinator d'hydration existant
aucune nouvelle notification n'est admise après cette borne dans pre.003
les signaux/tâches déjà admis sont drainés
la source termine ensuite par source.continuity_gap_proven
```
Ce séquencement évite de perdre artificiellement le premier événement post-incident tout en restant fail-closed tant que les tranches de repair ne sont pas implémentées.
## Standard Block
La source Standard Block observe le même snapshot latest-value Transport.
Le premier bloc post-incident :
```text
borne l'incident
projette et envoie toutes ses ingresses via l'admission centrale existante
marque ensuite le slot settled
termine enfin par source.continuity_gap_proven
```
Un bloc sans transaction qualifiée peut quand même fermer la borne de slot et est settled avant la terminalité conservative.
## Yellowstone
Le chemin Yellowstone existant reste inchangé fonctionnellement dans cette tranche :
```text
aucune mutation Worker de from_slot
aucun ownership Worker de SubscribeReplayInfo
aucune nouvelle tentative de replay
aucune modification du contrat Transport Yellowstone
```
La preuve de replay native et la distinction `replay_attempt` / couverture effective restent réservées à `pre.004`.
## Tests ajoutés/étendus
Transport couvre :
```text
source latest-value courante
publication d'un changement
fermeture de la source lorsque l'actor publisher disparaît
accessibilité crate-root de WsSessionSnapshotSource
présence des délégations Standard/Helius
réutilisation du watch actor existant sans second runtime
```
Worker couvre :
```text
ancre inclusive et bornée
extension monotone d'un incident reconnect/overflow
rejet d'un incident sans motif
rejet de range inversé ou surdimensionné
incident sans slot précédent -> erreur explicite
compteurs WebSocket régressifs -> erreur explicite
reconnect et overflow successifs -> même ancre la plus ancienne
absence de socket/retry/from_slot possédé par le Worker
```
## Fichiers ajoutés
```text
deltas/0.3.14/pre.003.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-onchain-transport-lib/src/lib.rs
crates/ksp-onchain-transport-lib/src/ws_protocol_session.rs
crates/ksp-onchain-transport-lib/src/ws_session.rs
crates/ksp-onchain-transport-lib/tests/public_api.rs
crates/ksp-onchain-transport-lib/tests/release_completeness.rs
crates/ksp-onchain-transport-lib/unit_tests/ws_session.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/continuity.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/continuity.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
```
## Fichiers supprimés
```text
aucun
```
## Version Cargo
Conformément à `VER-ID-009`, cette nouvelle prerelease synchronise la version workspace :
```text
header Cargo.toml : 569 -> 570
workspace.package.version : 0.3.14-pre.2.fix.2 -> 0.3.14-pre.3
```
Versions des fichiers modifiés :
```text
ksp-onchain-transport-lib/src/lib.rs : 47 -> 48
ksp-onchain-transport-lib/src/ws_protocol_session.rs : 6 -> 7
ksp-onchain-transport-lib/src/ws_session.rs : 14 -> 15
ksp-onchain-transport-lib/tests/public_api.rs : 52 -> 53
ksp-onchain-transport-lib/tests/release_completeness.rs : 44 -> 45
ksp-onchain-transport-lib/unit_tests/ws_session.rs : 11 -> 12
ksp-worker-raw-transaction-ingest-lib/src/continuity.rs : 3 -> 4
ksp-worker-raw-transaction-ingest-lib/src/lib.rs : 28 -> 29
ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs : 29 -> 30
ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs : 26 -> 27
ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs : 21 -> 22
ksp-worker-raw-transaction-ingest-lib/unit_tests/continuity.rs : 2 -> 3
ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs : 23 -> 24
```
## Frontières préservées
```text
aucun repair HTTP
aucun nouveau socket WebSocket
aucun second actor WebSocket
aucun retry Worker
aucune nouvelle dépendance
aucune nouvelle feature
aucun changement Config
aucun changement Store
aucun changement Job Backfill
aucun backend Store physique depuis Worker
aucune mutation Worker de Yellowstone from_slot
aucun SubscribeReplayInfo possédé par Worker
aucun élargissement de la façade publique Worker
src/lib.rs Worker reste exempt de ReplayInfo/from_slot/repair/backfill
```
## Validations exécutées
Dans le sandbox de préparation :
```text
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
scan exact ReplayInfo/from_slot/repair/backfill dans Worker src/lib.rs
scan de possession WebSocket directe dans Worker
scan set_from_slot dans les sources de production Worker
comparaison exacte 0.3.14-pre.002-fix.002 -> 0.3.14-pre.003
contrôle des versions de fichier modifiées
contrôle du contenu de l'archive delta
unzip -t de l'archive delta
réapplication de l'archive sur la base puis comparaison byte-exacte
```
## Validations non exécutées
Le sandbox de préparation ne fournit pas le toolchain Cargo/Rust. Les gates suivants restent donc explicitement à exécuter côté 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-onchain-transport-lib --all-targets --all-features
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features
```
## Décisions prises
```text
l'observabilité WS réutilise le watch latest-value déjà possédé par Transport
le Worker observe mais ne pilote pas la reconnexion WebSocket
un gap WS commence au dernier slot source réellement observé, inclusivement
le premier slot source post-incident est la borne de fin inclusive
aucune histoire antérieure n'est inventée sans ancre source
le premier événement post-incident est admis avant la terminalité conservative
pre.003 reste fail-closed et ne prétend pas encore réparer le gap
```
## Questions ouvertes
```text
aucune pour pre.003
```
## Tranche suivante
`pre.004` reste dédiée à la preuve de replay natif Yellowstone : qualification de `SubscribeReplayInfo`, preuve de couverture effective et conservation stricte de l'ownership `from_slot` dans Transport.
## Gate opérateur après application
```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-onchain-transport-lib --all-targets --all-features
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features
```

367
deltas/0.3.14/pre.004.md Normal file
View File

@@ -0,0 +1,367 @@
<!-- file: deltas/0.3.14/pre.004.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.004` — preuve conservative de replay natif Yellowstone
## Base requise
```text
0.3.14-pre.003
workspace.package.version = 0.3.14-pre.3
deltas/0.3.14/pre.003.md présent
```
## Gate de la base
Le gate opérateur de `0.3.14-pre.003` est validé avant ouverture de cette tranche :
```text
cargo fmt --all : PASS
cargo fmt --all -- --check : PASS
audit Rust workspace rules : PASS
audit Markdown tables : PASS
cargo check --workspace : PASS
cargo clippy --workspace --all-targets --all-features -- -D warnings : PASS
cargo test -p ksp-onchain-transport-lib --all-targets --all-features : PASS
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features : PASS
```
Le gate Transport comprend notamment :
```text
390 unit tests : PASS
public_api : 53 PASS
release_completeness : 45 PASS
smokes live : ignorés par défaut conformément au contrat existant
```
Le gate Worker comprend notamment :
```text
119 unit tests : PASS
cross_layer_completeness : 8 PASS
dependency_boundary : 19 PASS
hardening : 28 PASS
public_api : 20 PASS
release_completeness : 5 PASS
```
## Objectif
Implémenter strictement la tranche `pre.004` du plan `035` :
```text
conserver l'ownership Yellowstone from_slot / SubscribeReplayInfo dans Transport
distinguer replay attempt de replay delivery
distinguer toute delivery ponctuelle d'une preuve de coverage
conserver first_available comme preuve de limite de rétention uniquement
rendre observable une reprise acceptée dont la couverture reste non prouvée
rester fail-closed côté Worker tant que les tranches de réconciliation ne sont pas présentes
ne créer aucun replay Worker, aucun repair HTTP et aucune nouvelle source
```
## Transport — distinction attempt / delivery / coverage non prouvée
`YellowstoneGrpcSubscribeSnapshot` conserve `replay_attempt_count` et ajoute deux compteurs sûrs :
```text
replay_delivery_count
replay_coverage_unproven_count
```
La sémantique est volontairement conservative.
`replay_delivery_count` n'augmente que lorsqu'une reconnexion replay-bearing redélivre exactement le slot de reprise effectivement demandé par Transport. Cette observation prouve uniquement qu'un matériau slot-bearing a été livré au bord demandé. Elle ne prouve pas que tous les updates correspondant aux filtres ont été rejoués sur l'intervalle.
`replay_coverage_unproven_count` augmente lorsqu'une reconnexion replay-bearing a réussi et que :
```text
le premier update slot-bearing post-reconnect atteint ou dépasse la borne demandée
ou
un nouveau reconnect commence avant tout matériau slot-bearing replayé
```
Même une redélivrance exacte de la borne fait donc progresser `replay_delivery_count` **et** `replay_coverage_unproven_count` : la première prouve une delivery ponctuelle, la seconde conserve explicitement l'absence de preuve de couverture complète.
Cette valeur signifie explicitement **coverage non prouvée**. Elle ne prouve pas qu'un événement filtré existait, qu'il a été perdu, ni que le provider a nécessairement violé son contrat.
Aucun compteur générique `replay_covered` / `replay_coverage_proven` n'est ajouté. Le replay natif Transport ne possède pas aujourd'hui une preuve générique suffisante pour faire cette affirmation.
## Transport — tracker de replay
Le `ContinuityTracker` existant conserve maintenant une seule borne privée :
```text
pending_replay_from_slot
```
Lorsqu'un reconnect physique Yellowstone réussit avec un `effective_from_slot`, Transport ouvre cette preuve pending.
Sur le premier update slot-bearing :
```text
slot == requested -> delivery conservatrice prouvée + coverage globale toujours non prouvée
slot > requested -> aucune delivery de borne prouvée + coverage globale non prouvée
slot < requested -> aucune conclusion positive ; la preuve reste pending
```
Si le stream se coupe de nouveau avant résolution de la borne pending, la coverage de cette tentative est classée non prouvée avant d'ouvrir la tentative suivante.
Les `Ping`/`Pong` ne ferment jamais cette preuve parce qu'ils ne portent aucun slot.
## Transport — rétention `SubscribeReplayInfo`
Le contrat antérieur reste inchangé :
```text
first_available > requested
```
incrémente `continuity_gap_count`, clamp le `from_slot` effectif dans Transport et signifie uniquement que l'endpoint ne peut pas rejouer avant `first_available`.
Le clamp ne devient pas une preuve de perte d'un événement filtré et ne devient pas une preuve de coverage complète.
## Cas replay accepté mais couverture non prouvée
Une fixture dédiée couvre le cas adversarial prévu au plan :
```text
avant coupure : slot 800 observé
reconnect : from_slot = 800 accepté
premier update après reconnect : slot 805
```
Résultat attendu :
```text
reconnect_count = 1
replay_attempt_count = 1
replay_delivery_count = 0
replay_coverage_unproven_count = 1
continuity_gap_count = 0
```
Le résultat ne prétend ni qu'un update manquait réellement entre 800 et 805, ni que le replay est complet. Il démontre seulement que KSP ne possède pas la preuve conservative de delivery requise pour assimiler la reprise à une couverture saine.
La fixture de replay redélivrant `slot 500` conserve au contraire :
```text
replay_attempt_count = 1
replay_delivery_count = 1
replay_coverage_unproven_count = 1
```
Cette delivery ne devient toujours pas un `replay_covered`.
## Worker — consommation des preuves Transport
`RawTransactionIngestProcessingFrontierReporter` observe désormais de manière monotone :
```text
replay_attempt_total
replay_delivery_total
replay_coverage_unproven_total
continuity_gap_total
```
Le Worker ne calcule aucun `from_slot`, n'appelle jamais `SubscribeReplayInfo` et ne construit aucune requête replay.
La phase `replay_attempt` peut être observée avant toute delivery et ne ferme aucune obligation de coverage. Une delivery de borne fait ensuite progresser la preuve de delivery mais conserve simultanément `coverage_unproven`.
Une progression de `replay_coverage_unproven_total` sans gap de rétention produit actuellement :
```text
source.replay_coverage_unproven
```
et conserve la terminalité fail-closed de la source. Cette terminalité est volontairement temporaire jusqu'aux tranches de coverage redondante et de réconciliation du plan `035` ; elle évite qu'un replay accepté mais non prouvé soit assimilé silencieusement à une continuité saine.
Si `continuity_gap_total` augmente simultanément, la preuve de rétention plus forte conserve la priorité et produit le contrat existant :
```text
source.continuity_gap_proven
```
Les nouveaux compteurs privés sont soumis au même rejet de régression que les compteurs reconnect/replay existants.
## Surface publique
Transport étend uniquement le snapshot Yellowstone déjà public avec :
```text
YellowstoneGrpcSubscribeSnapshot::replay_delivery_count()
YellowstoneGrpcSubscribeSnapshot::replay_coverage_unproven_count()
```
Aucun type provider, payload, endpoint, credential ou proto upstream n'est exposé.
La façade publique Worker n'est pas étendue. Les preuves supplémentaires restent privées au runtime Worker dans cette tranche.
## Documentation d'utilisation
`ksp-onchain-transport-lib/USAGE.md` explicite désormais que :
```text
attempt != delivery != coverage
une delivery ponctuelle n'est pas une preuve de replay_covered
coverage_unproven est une absence explicite de preuve, pas une preuve de perte
```
Le document reste version-neutral conformément aux règles workspace.
## Tests ajoutés/étendus
Transport couvre :
```text
replay avec redélivrance exacte de la borne -> delivery = 1
rétention first_available -> gap = 1 tout en conservant delivery distincte
reconnects exhaustés -> attempts sans fausse delivery
replay accepté puis progression au-delà de la borne -> coverage_unproven = 1
absence de compteur générique replay_covered/replay_coverage_proven
accessibilité crate-root des deux getters snapshot
```
Worker couvre :
```text
attempt seul != delivery
delivery de borne != coverage
coverage_unproven devient fail-closed
régression delivery/coverage_unproven rejetée
absence persistante de set_from_slot / SubscribeReplayInfo / YellowstoneReplayInfo dans Worker
```
## Fichiers ajoutés
```text
deltas/0.3.14/pre.004.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-onchain-transport-lib/USAGE.md
crates/ksp-onchain-transport-lib/src/grpc_stream.rs
crates/ksp-onchain-transport-lib/tests/public_api.rs
crates/ksp-onchain-transport-lib/tests/release_completeness.rs
crates/ksp-onchain-transport-lib/unit_tests/grpc_stream.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
```
## Fichiers supprimés
```text
aucun
```
## Version Cargo
Conformément à `VER-ID-009`, cette nouvelle prerelease synchronise la version workspace :
```text
header Cargo.toml : 570 -> 571
workspace.package.version : 0.3.14-pre.3 -> 0.3.14-pre.4
```
Versions des fichiers modifiés :
```text
ksp-onchain-transport-lib/USAGE.md : 25 -> 26
ksp-onchain-transport-lib/src/grpc_stream.rs : 4 -> 5
ksp-onchain-transport-lib/tests/public_api.rs : 53 -> 54
ksp-onchain-transport-lib/tests/release_completeness.rs : 45 -> 46
ksp-onchain-transport-lib/unit_tests/grpc_stream.rs : 5 -> 6
ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs : 30 -> 31
ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs : 27 -> 28
ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs : 22 -> 23
ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs : 24 -> 25
```
## Frontières préservées
```text
from_slot reste Transport-owned
SubscribeReplayInfo reste Transport-owned
aucun set_from_slot dans Worker
aucun replay loop Worker
aucun retry réseau Worker
aucun repair HTTP
aucune nouvelle source
aucun nouveau provider
aucune nouvelle dépendance
aucune nouvelle feature
aucun changement Config
aucun changement Store
aucun changement Job Backfill
aucun backend Store physique depuis Worker
aucun replay_covered générique inventé par Transport
aucun élargissement de la façade publique Worker
```
## Validations exécutées
Dans le sandbox de préparation :
```text
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
scan exact de possession replay dans Worker
scan des nouveaux compteurs attempt/delivery/coverage_unproven
comparaison exacte 0.3.14-pre.003 -> 0.3.14-pre.004
contrôle des versions de fichier modifiées
contrôle du contenu de l'archive delta
unzip -t de l'archive delta
réapplication de l'archive sur la base puis comparaison byte-exacte
```
## Validations non exécutées
Le sandbox de préparation ne fournit pas le toolchain Cargo/Rust. Les gates suivants restent donc explicitement à exécuter côté 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-onchain-transport-lib --all-targets --all-features
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features
```
## Décisions prises
```text
replay attempt ne prouve aucune delivery
delivery au slot demandé ne prouve aucune coverage complète
absence de delivery conservative avant progression/reconnect suivant devient coverage_unproven
first_available reste une preuve de rétention et non une preuve d'événement perdu
aucun replay_covered n'est revendiqué par Transport générique
le Worker reste fail-closed sur coverage_unproven jusqu'aux tranches de réconciliation
```
## Questions ouvertes
```text
aucune pour pre.004
```
## Tranche suivante
`pre.005` reste dédiée à la coverage redondante : relations exact/superset conservatrices, epochs/ranges de coverage et refus explicite des équivalences cross-family opportunistes.
## Gate opérateur après application
```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-onchain-transport-lib --all-targets --all-features
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features
```

320
deltas/0.3.14/pre.005.md Normal file
View File

@@ -0,0 +1,320 @@
<!-- file: deltas/0.3.14/pre.005.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.005` — preuve conservative de coverage redondante
## Base requise
```text
0.3.14-pre.004
workspace.package.version = 0.3.14-pre.4
deltas/0.3.14/pre.004.md présent
```
## Gate de la base
Le gate opérateur de `0.3.14-pre.004` est validé avant ouverture de cette tranche :
```text
cargo fmt --all : PASS
cargo fmt --all -- --check : PASS
audit Rust workspace rules : PASS
audit Markdown tables : PASS
cargo check --workspace : PASS
cargo clippy --workspace --all-targets --all-features -- -D warnings : PASS
cargo test -p ksp-onchain-transport-lib --all-targets --all-features : PASS
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features : PASS
```
Le gate Transport comprend notamment :
```text
391 unit tests : PASS
public_api : 53 PASS
release_completeness : 46 PASS
smokes live : ignorés par défaut conformément au contrat existant
```
Le gate Worker comprend notamment :
```text
120 unit tests : PASS
cross_layer_completeness : 8 PASS
dependency_boundary : 19 PASS
hardening : 29 PASS
public_api : 20 PASS
release_completeness : 6 PASS
```
## Objectif
Implémenter strictement la tranche `pre.005` du plan `035` :
```text
matérialiser les relations conservative Exact / Superset
matérialiser des epochs et ranges de coverage run-local bornés
n'autoriser une preuve redondante que depuis une source distincte
exiger un commitment strictement identique
exiger que l'epoch couvre intégralement la plage du gap
interdire toute équivalence opportuniste entre familles exactes différentes
conserver KnownReferences impropre à TargetCoverage
ne modifier encore aucune décision runtime de supervisor
ne déclencher aucun repair HTTP et aucun replay supplémentaire
```
## Relations de coverage
`RawTransactionIngestCoverageScope` possède maintenant une relation privée et directionnelle :
```text
FullLedgerTransactions -> FullLedgerTransactions : Exact
FullLedgerTransactions -> ExactSourceScope(...) : Superset
ExactSourceScope(family, fingerprint)
-> ExactSourceScope(même family, même fingerprint) : Exact
autres combinaisons : aucune relation prouvée
```
Cette relation est volontairement conservative.
Deux scopes exacts qui partagent le même fingerprint mais pas le même `family_code` ne sont jamais considérés équivalents. Ainsi, par exemple, aucune correspondance `standard_logs <-> helius_transaction` ou `standard_block <-> yellowstone` n'est inventée à partir d'une similarité de sélecteur.
La relation est également indépendante de l'identité provider/endpoint : cette indépendance vient uniquement du scope sémantique déjà normalisé, tandis que l'obligation de redondance exige une `source_key` différente.
## Commitments
La redondance requiert un commitment strictement identique :
```text
Confirmed couvre seulement Confirmed
Finalized couvre seulement Finalized
```
Aucune hiérarchie `Confirmed <-> Finalized` n'est inférée dans cette tranche.
## TargetCoverage
La construction de `RawTransactionIngestTargetCoverage` réutilise désormais la relation conservative plutôt qu'une comparaison ad hoc :
```text
un FullLedgerTransactions au même commitment subsume les requirements exacts
un requirement exact identique est dédupliqué
un scope exact cross-family reste distinct
un commitment différent reste distinct
KnownReferences reste interdit comme target scope
```
Aucune broadening cross-family n'est introduite.
## Epochs et ranges de coverage
La tranche matérialise des contrats privés :
```text
RawTransactionIngestCoverageRange
RawTransactionIngestCoverageEpoch
RawTransactionIngestCoverageEpochLedger
RawTransactionIngestCoverageRelation
```
Un `CoverageRange` est inclusif et rejette uniquement une plage inversée. Contrairement au `GapRange`, il n'est pas limité artificiellement à `MAX_RAW_TRANSACTION_INGEST_REPAIR_RANGE_SLOTS`, car un epoch de continuité saine peut être plus long qu'une seule obligation de repair.
Un epoch conserve :
```text
epoch_id
source_key
commitment
scope
range inclusif
```
Le ledger est run-local et borné à :
```text
MAX_RAW_TRANSACTION_INGEST_COVERAGE_EPOCHS = 256
```
Il vérifie :
```text
IDs non nuls et uniques
next_epoch_id strictement monotone
borne de cardinalité
range valide
scope admissible comme configured target scope
source_key présente dans l'inventaire exact des capabilities
commitment et scope strictement identiques à la capability de cette source
```
Le ledger est initialisé vide avec les autres continuity contracts. Aucune coverage n'est pré-déclarée à partir de la simple configuration d'une source.
## Preuve de redondance
Un epoch peut prouver une obligation uniquement si toutes les conditions suivantes sont vraies :
```text
source de preuve != source cible
commitment de preuve == commitment requis
range de preuve contient intégralement le GapRange inclusif
scope de preuve relation Exact ou Superset avec le scope requis
```
Un epoch partiel, une source identique, un commitment différent, un filtre différent ou une famille différente ne ferment donc aucune obligation.
`FullLedgerTransactions` peut fournir une preuve `Superset` d'un scope exact au même commitment, parce qu'il représente toutes les transactions de tous les blocs produits. L'inverse est interdit.
## Activation runtime
Cette tranche ne change pas encore la politique de source-loss/reconciliation :
```text
aucun gap n'est fermé automatiquement
aucune source n'est maintenue en vie grâce à cette preuve
aucune terminalité fail-closed de pre.003/pre.004 n'est encore remplacée
```
`pre.008` restera responsable de l'intégration de ces preuves dans le supervisor et le gap ledger. `pre.005` fournit uniquement le modèle conservative nécessaire.
## Tests ajoutés
Les unit tests Worker couvrent :
```text
Exact pour même famille + même fingerprint
Superset uniquement FullLedger -> exact
absence de relation exact -> FullLedger
absence de relation pour filtre exact différent
absence de relation cross-family malgré fingerprint identique
source distincte obligatoire
commitment strictement identique
range complet obligatoire
ledger borné à 256 epochs
next_epoch_id monotone
capability source/scope/commitment obligatoire
```
Les canaris `hardening` et `release_completeness` garantissent en plus :
```text
présence des contrats Exact/Superset/epoch
absence d'équivalence cross-family codée en dur
absence de hiérarchie opportuniste de commitment
absence d'exposition publique des nouveaux types de preuve
```
## Fichiers ajoutés
```text
deltas/0.3.14/pre.005.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/continuity.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/continuity.rs
```
## Fichiers supprimés
```text
aucun
```
## Version Cargo
Conformément à `VER-ID-009`, cette nouvelle prerelease synchronise la version workspace :
```text
header Cargo.toml : 571 -> 572
workspace.package.version : 0.3.14-pre.4 -> 0.3.14-pre.5
```
Versions des fichiers modifiés :
```text
ksp-worker-raw-transaction-ingest-lib/src/continuity.rs : 4 -> 5
ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs : 28 -> 29
ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs : 23 -> 24
ksp-worker-raw-transaction-ingest-lib/unit_tests/continuity.rs : 3 -> 4
```
## Frontières préservées
```text
aucun changement Transport
aucun changement Config
aucun changement Store
aucun changement Job Backfill
aucune nouvelle dépendance
aucune nouvelle feature
aucun repair HTTP
aucun replay Worker
aucun nouveau socket/actor/retry
aucun set_from_slot dans Worker
aucune nouvelle surface publique Worker
aucune équivalence cross-family opportuniste
aucune hiérarchie implicite de commitment
```
## Validations exécutées dans le sandbox de préparation
```text
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
scan des frontières et de la surface crate-root Worker
comparaison exacte 0.3.14-pre.004 -> 0.3.14-pre.005
contrôle des versions de fichiers modifiés
contrôle du contenu de l'archive delta
unzip -t de l'archive delta
réapplication de l'archive sur la base puis comparaison byte-exacte
```
## Validations non exécutées dans le sandbox de préparation
Le sandbox ne fournit pas le toolchain Cargo/Rust. Les gates suivants restent à exécuter côté 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-worker-raw-transaction-ingest-lib --all-targets --all-features
```
## Décisions prises
```text
coverage relationnelle directionnelle, jamais symétrisée implicitement
FullLedger est un superset d'un exact scope au même commitment
un exact scope n'est équivalent qu'à même family + même fingerprint
une redondance doit provenir d'une source_key distincte
un epoch doit contenir toute la plage du gap
aucune coverage initiale n'est créée depuis la configuration seule
pre.005 ne modifie pas encore la terminalité runtime
```
## Questions ouvertes
```text
aucune pour pre.005
```
## Tranche suivante
`pre.006` reste dédiée au scan HTTP de réparation : détection des capabilities run-wide réellement disponibles, fenêtres bornées, découverte `getBlocks`/`getBlocksWithLimit` conservative et récupération `getBlock observed`, sans broadening de scope.
## Gate opérateur après application
```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-worker-raw-transaction-ingest-lib --all-targets --all-features
```

274
deltas/0.3.14/pre.006.md Normal file
View File

@@ -0,0 +1,274 @@
<!-- file: deltas/0.3.14/pre.006.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.006` — discovery HTTP bornée pour continuité
## Base requise
```text
0.3.14-pre.005
workspace.package.version = 0.3.14-pre.5
deltas/0.3.14/pre.005.md présent
```
## Gate de la base
Le gate opérateur de `0.3.14-pre.005` est validé avant ouverture de cette tranche :
```text
cargo fmt --all : PASS
cargo fmt --all -- --check : PASS
audit Rust workspace rules : PASS
audit Markdown tables : PASS
cargo check --workspace : PASS
cargo clippy --workspace --all-targets --all-features -- -D warnings : PASS
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features : PASS
```
Le gate Worker comprend notamment :
```text
124 unit tests : PASS
cross_layer_completeness : 8 PASS
dependency_boundary : 19 PASS
hardening : 30 PASS
public_api : 20 PASS
release_completeness : 7 PASS
```
## Objectif
Implémenter strictement la tranche `pre.006` du plan `035` :
```text
détecter les primitives HTTP de scan réellement disponibles sur le rôle same-network
borner chaque fenêtre de discovery à 512 slots inclusifs
préférer getBlocks(start,end) lorsqu'il est réellement supporté
utiliser getBlocksWithLimit uniquement avec une preuve de progression issue de sa propre réponse
ne jamais utiliser getSlot seul comme preuve de queue skipped
réutiliser getBlock observed pour les slots explicitement produits
conserver getBlock = null comme obstacle, jamais comme slot skipped
ne pas élargir un scope filtré vers FullLedgerTransactions
ne pas modifier encore la politique de supervisor ou de health
ne pas introduire de logique caller-driven de backfill
```
## Capability HTTP run-wide
`runtime_resources.rs` matérialise maintenant un inventaire privé :
```text
RawTransactionIngestHttpScanCapabilities
get_block
get_blocks
get_blocks_with_limit
get_slot
```
Le choix de primitive est conservative :
```text
getBlock + getBlocks
-> ClosedRange
getBlock + getBlocksWithLimit + getSlot
-> WithLimitBoundary
autres combinaisons
-> scan indisponible
```
`ClosedRange` est toujours préféré lorsque les deux stratégies sont disponibles.
La détection reste fondée sur le snapshot Transport du rôle exact et du cluster attendu. La seule présence d'un pool HTTP ne crée donc aucune capability implicite.
## Fenêtres bornées
La borne canonique de `continuity.rs` reste :
```text
MAX_RAW_TRANSACTION_INGEST_REPAIR_DISCOVERY_WINDOW_SLOTS = 512
```
Un alias crate-wide de même valeur est ajouté uniquement pour que `runtime_resources.rs` réutilise cette borne sans la dupliquer :
```text
MAX_RAW_TRANSACTION_INGEST_CONTINUITY_DISCOVERY_WINDOW_SLOTS
```
Une fenêtre :
```text
est inclusive
rejette end < start
rejette les overflows
rejette plus de 512 slots
```
La source HTTP polling productive réutilise désormais cette primitive. Sa valeur `max_discovered_blocks_per_cycle` reste une borne supérieure ; une fenêtre productive ne dépasse jamais le minimum entre cette valeur et `512` slots.
## Stratégie `ClosedRange`
Lorsque `getBlocks` est supporté, le Worker appelle :
```text
getBlocks(start_slot, end_slot, commitment)
```
Une réponse réussie constitue une preuve fermée de la fenêtre RPC demandée. Les slots absents de la liste sont alors qualifiables comme `skipped/non-produced` uniquement à l'intérieur de cette fenêtre.
Tout slot retourné au-delà de `end_slot` est rejeté comme violation d'invariant.
## Stratégie `WithLimitBoundary`
Lorsque seul le chemin `getBlocksWithLimit` qualifié est disponible :
```text
getSlot(commitment)
getBlocksWithLimit(start_slot, window_slot_count, commitment)
```
`getSlot` indique uniquement la position du cluster observée par ce rôle. Il ne ferme jamais à lui seul la queue de la fenêtre.
La preuve avance seulement jusqu'au dernier slot produit effectivement énuméré par la réponse `getBlocksWithLimit` :
```text
last_discovered < end_slot
-> preuve partielle jusqu'à last_discovered
last_discovered >= end_slot
-> fenêtre cible entièrement prouvée
aucun slot découvert
-> aucune preuve de tail, même si getSlot >= end_slot
```
Ainsi, une queue constituée uniquement de slots skipped n'est pas inventée à partir de deux réponses pouvant provenir de routes HTTP différentes. La fenêtre restera en attente jusqu'à ce qu'une primitive plus forte ou un bloc ultérieur fournisse une borne observable.
## `getBlock observed`
Chaque slot explicitement produit continue à passer par :
```text
HttpTransportPool::get_block_observed
```
Les règles existantes restent inchangées :
```text
Some(block)
-> projection Common RAW puis admission centrale
None
-> la progression se bloque sur ce slot
le slot n'est pas reclassé skipped
```
Aucun second pipeline de persistence n'est ajouté.
## Scope et intégration
Cette tranche utilise immédiatement la primitive bornée uniquement dans `RawTransactionIngestHttpBlockPollingSource`, dont le scope est déjà `FullLedgerTransactions`.
Pour Yellowstone, Standard Logs et Helius Transaction, la détection run-wide de capability HTTP demeure disponible dans les continuity descriptors, mais aucun scan full-block n'est déclenché ici. Leur activation dépendra de la preuve de scope et de la réconciliation des tranches suivantes.
`Standard Block` conserve l'absence de pool HTTP attaché.
La politique de terminalité des incidents `pre.003/pre.004` n'est pas encore remplacée. `pre.008` reste responsable de l'intégration supervisor/gap-ledger.
## Tests ajoutés
Les unit tests Worker couvrent :
```text
préférence ClosedRange sur fallback
capability fallback complète obligatoire
capability détectée par rôle sans I/O
fenêtre inclusive 1..512
rejet 513 slots, range inversé et overflow
ClosedRange vide comme preuve de slots skipped dans la fenêtre exacte
rejet d'un résultat ClosedRange au-delà de end_slot
fallback dépassant end_slot et filtrage de la cible
fallback partiel ne prouvant que jusqu'au dernier slot énuméré
getSlot seul + réponse vide ne prouvant aucune queue
absence de getSlot dans le fallback rejetée
```
Les canaris `hardening` et `release_completeness` garantissent en plus :
```text
présence des deux stratégies HTTP
usage borné de getBlocks/getBlocksWithLimit
réutilisation de getBlock observed
absence de dépendance Job Backfill
absence de surface publique de discovery
absence de responsabilité repair lower-case dans la crate-root historique
```
## Hors périmètre inchangé
```text
aucun moteur de reconciliation supervisor
aucun changement health public
aucune hydration repair getTransaction dans cette tranche
aucune source/provider supplémentaire
aucun EARLY/shred adapter
aucun backfill historique caller-driven
```
## Fichiers ajoutés
```text
deltas/0.3.14/pre.006.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/continuity.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
```
## Fichiers supprimés
```text
aucun
```
## Version Cargo
Conformément à `VER-ID-009` :
```text
header Cargo.toml : 572 -> 573
workspace.package.version : 0.3.14-pre.5 -> 0.3.14-pre.6
```
Versions des fichiers modifiés :
```text
continuity.rs : 5 -> 6
lib.rs : 29 -> 30
runtime_resources.rs : 31 -> 32
unit_tests/runtime_resources.rs : 25 -> 26
tests/hardening.rs : 29 -> 30
tests/release_completeness.rs : 24 -> 25
```
## Validation exécutée dans l'environnement de préparation
```text
python3 scripts/audit_rust_workspace_rules.py : PASS
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas : PASS
```
Les gates Cargo ne sont pas déclarés PASS dans l'environnement de préparation lorsqu'ils ne peuvent pas y être exécutés. Ils restent obligatoires côté opérateur avant `pre.007`.
## Prochaine tranche
`pre.007` : hydration des références connues avec `getTransaction observed`, coalescence globale existante et conservation de `Missing` comme obligation non résolue ; aucun `getTransaction = null` ne devra devenir une preuve d'absence.

View File

@@ -0,0 +1,172 @@
<!-- file: deltas/0.3.14/pre.007-fix.001.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.007-fix.001` — correction `large_enum_variant`
## Base requise
```text
0.3.14-pre.007
workspace.package.version = 0.3.14-pre.7
deltas/0.3.14/pre.007.md présent
```
## Objectif
Corriger strictement la tranche `pre.007` après le gate opérateur, sans modifier sa responsabilité fonctionnelle :
```text
faire passer Clippy -D warnings sans allow/expect
conserver exactement la distinction Available/Missing de l'hydration known-reference
conserver le registre global d'hydration unique
ne pas avancer la réconciliation prévue en pre.008
```
## Défaut observé
Le gate opérateur sur `0.3.14-pre.7` a produit :
```text
cargo fmt --all : PASS
cargo fmt --all -- --check : PASS
audit Rust workspace rules : PASS
audit Markdown tables : PASS
cargo check --workspace : PASS
cargo clippy --workspace --all-targets --all-features -- -D warnings : FAIL
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features : PASS
```
Clippy refuse `RawTransactionIngestKnownReferenceHydrationResolution` avec `clippy::large_enum_variant` :
```text
Available(RawTransactionIngress) : au moins 488 octets
Missing { ... } : au moins 105 octets
```
Les `133` unit tests Worker ainsi que les suites d'intégration exécutées passent ; le défaut est donc un défaut de représentation mémoire détecté par le gate strict, pas un échec fonctionnel observé.
## Cause
`pre.007` transporte directement `RawTransactionIngress` dans la variante `Available` de l'enum privé de résolution known-reference.
`RawTransactionIngress` est nettement plus volumineux que la variante `Missing`, ce qui force chaque valeur de l'enum à avoir la taille de la variante la plus grande et déclenche le lint `large_enum_variant` sous `-D warnings`.
## Correction
La variante disponible devient :
```text
Available(Box<RawTransactionIngress>)
```
La valeur est boxée uniquement au moment où `finalize_hydration` fournit un ingress disponible, puis immédiatement déboxée au point de consommation avant admission dans le pipeline commun.
Le unit test `pre.007` qui injecte directement cette résolution dans `RawTransactionAdmission` déboxe également la valeur.
Aucune variante, disposition, obligation, clé de coalescence, requête HTTP, provenance ou décision de runtime ne change.
## Fichiers ajoutés
```text
deltas/0.3.14/pre.007-fix.001.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
```
## Fichiers supprimés
```text
aucun
```
## Version Cargo
Le fix modifie du code Rust ; conformément à `VER-ID-007` et `VER-ID-010` :
```text
header Cargo.toml : 574 -> 575
workspace.package.version : 0.3.14-pre.7 -> 0.3.14-pre.7.fix.1
```
Versions de fichiers :
```text
src/runtime_resources.rs : 33 -> 34
unit_tests/runtime_resources.rs : 27 -> 28
```
## Frontières préservées
```text
aucune nouvelle tâche
aucun nouveau scheduler
aucun nouveau registre d'hydration
aucun nouveau retry Worker
aucune nouvelle requête HTTP
aucune nouvelle dépendance
aucune nouvelle feature
aucun accès Config depuis Worker
aucun accès Job Backfill depuis Worker
aucun backend Store physique depuis Worker
aucune extension de surface publique
aucune intégration anticipée du gap ledger/supervisor de pre.008
```
## Validations exécutées
Dans le sandbox de préparation :
```text
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
scan des usages de RawTransactionIngestKnownReferenceHydrationResolution
comparaison exacte pre.007 -> pre.007-fix.001
contrôle du contenu de l'archive delta
unzip -t de l'archive delta
```
## Validations non exécutées
Le sandbox de préparation ne fournit pas le toolchain Cargo/Rust. Les gates suivants restent à exécuter côté 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-worker-raw-transaction-ingest-lib --all-targets --all-features
```
## Décisions prises
```text
fix strict de pre.007
indirection uniquement sur la grosse variante Available
aucun allow/expect Clippy
aucune modification fonctionnelle de l'hydration known-reference
pre.008 reste la première tranche autorisée à intégrer la réconciliation au supervisor/gap ledger
```
## Questions ouvertes
```text
aucune pour ce fix
```
## Gate opérateur après application
```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-worker-raw-transaction-ingest-lib --all-targets --all-features
```

217
deltas/0.3.14/pre.007.md Normal file
View File

@@ -0,0 +1,217 @@
<!-- file: deltas/0.3.14/pre.007.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.007` — hydration des références connues
## Base requise
```text
0.3.14-pre.006
workspace.package.version = 0.3.14-pre.6
deltas/0.3.14/pre.006.md présent
```
## Gate de la base
Le gate opérateur de `0.3.14-pre.006` est validé avant ouverture de cette tranche :
```text
cargo fmt --all : PASS
cargo fmt --all -- --check : PASS
audit Rust workspace rules : PASS
audit Markdown tables : PASS
cargo check --workspace : PASS
cargo clippy --workspace --all-targets --all-features -- -D warnings : PASS
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features : PASS
```
Le gate Worker comprend notamment :
```text
129 unit tests : PASS
cross_layer_completeness : 8 PASS
dependency_boundary : 19 PASS
hardening : 31 PASS
public_api : 20 PASS
release_completeness : 8 PASS
```
## Objectif
Implémenter strictement la tranche `pre.007` du plan `035` :
```text
réutiliser getTransaction observed pour une référence déjà connue
réutiliser le registre global de coalescence network/signature/commitment existant
ne créer aucun second registre d'hydration
représenter getTransaction = null comme une obligation Missing, jamais comme une preuve d'absence
conserver exactement le slot déjà observé avec la référence
préférer le bloc de ce slot lorsque le même rôle HTTP expose getBlock
ne pas déclencher encore la réconciliation du gap-ledger ni modifier le supervisor
ne pas ajouter de retry réseau Worker
```
## Obligation de référence connue
`continuity.rs` matérialise un contrat privé :
```text
RawTransactionIngestKnownReferenceObligation
reference : network + signature déjà observés
slot : slot déjà observé
commitment : Confirmed ou Finalized exact
```
Le contrat refuse `Processed` et ne contient aucun état « absent ». Il ne peut donc pas convertir une réponse HTTP `null` en preuve que la transaction n'existait pas.
Cette obligation reste distincte de `TargetCoverage` : `KnownReferences` ne devient toujours jamais une configured target coverage.
## Réutilisation de la coalescence globale
Le chemin productif existant reste fondé sur :
```text
RawTransactionIngestGlobalHydrationRegistry
RawTransactionIngestHydrationKey
network
signature
commitment
fetch_hydration_shared(...)
fetch_hydration(...)
HttpTransportPool::get_transaction_observed(...)
```
Aucun registre `KnownReferenceHydrationRegistry`, semaphore parallèle ou second fanout HTTP n'est ajouté.
Deux demandes concurrentes portant la même clé de référence partagent donc toujours le leader/follower global déjà présent depuis `0.3.13`.
## Résolution de l'hydration
La qualification après `getTransaction observed` devient explicite :
```text
Available(ingress)
Missing {
obligation,
disposition
}
```
`Available` réutilise exactement `finalize_hydration` et le pipeline central Common RAW -> admission -> Store existant.
`Missing` conserve la référence et son slot. Il ne produit aucun ingress et ne ferme aucune obligation de continuité.
Le coordinator nominal existant conserve son comportement stable jusqu'à `pre.008` : lorsqu'une notification live ordinaire hydrate vers `null`, son pending de processing est soldé comme auparavant. La nouvelle représentation `Missing` est toutefois la primitive réutilisable par la réconciliation ; `pre.008` décidera explicitement de la conservation dans le gap-ledger au lieu de confondre ce résultat avec une absence prouvée.
## Préférence bloc du slot
Après un `getTransaction = null`, aucune requête bloc n'est lancée automatiquement dans cette tranche.
Le rôle HTTP exact est inspecté sans I/O supplémentaire :
```text
getBlock supporté sur le même rôle
-> PreferBlockSlot
getBlock non supporté
-> AwaitCoverage
```
`PreferBlockSlot` signifie uniquement que le slot déjà connu est la prochaine primitive de matériau à privilégier lors de la réconciliation. Cela n'autorise ni scan historique, ni broadening du scope filtré, ni ingestion de transactions inconnues du bloc.
Le scan/discovery borné de `pre.006` et cette préférence de matériau restent séparés jusqu'à l'unification `pre.008`.
## Tests ajoutés
Les unit tests couvrent :
```text
obligation connue conservant exactement reference/slot/commitment
rejet du commitment Processed
coalescence de deux hydrations concurrentes vers une seule requête getTransaction
résolution Available vers le pipeline Common RAW existant
getTransaction = null sans getBlock -> Missing/AwaitCoverage
getTransaction = null avec getBlock sur le même rôle -> Missing/PreferBlockSlot
```
Les canaris `hardening` et `release_completeness` garantissent en plus :
```text
un seul RawTransactionIngestGlobalHydrationRegistry
aucun second KnownReferenceHydrationRegistry
réutilisation de fetch_hydration_shared et get_transaction_observed
aucune nouvelle surface publique
aucune responsabilité lower-case repair dans crate-root
```
## Hors périmètre inchangé
```text
aucune conservation active du Missing dans le gap-ledger avant pre.008
aucun getBlock fallback exécuté automatiquement dans cette tranche
aucune modification du supervisor source-loss
aucune modification de la health policy
aucun retry réseau Worker parallèle à Transport
aucune source/provider supplémentaire
aucun EARLY/shred adapter
aucun backfill historique caller-driven
```
## Fichiers ajoutés
```text
deltas/0.3.14/pre.007.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/continuity.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/continuity.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
```
## Fichiers supprimés
```text
aucun
```
## Version Cargo
Conformément à `VER-ID-009` :
```text
header Cargo.toml : 573 -> 574
workspace.package.version : 0.3.14-pre.6 -> 0.3.14-pre.7
```
Versions des fichiers modifiés :
```text
continuity.rs : 6 -> 7
lib.rs : 30 -> 31
runtime_resources.rs : 32 -> 33
unit_tests/continuity.rs : 4 -> 5
unit_tests/runtime_resources.rs : 26 -> 27
tests/hardening.rs : 30 -> 31
tests/release_completeness.rs : 25 -> 26
```
## Validation exécutée dans l'environnement de préparation
```text
python3 scripts/audit_rust_workspace_rules.py : PASS
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas : PASS
```
Les gates Cargo ne sont pas déclarés PASS dans l'environnement de préparation lorsqu'ils ne peuvent pas y être exécutés. Ils restent obligatoires côté opérateur avant `pre.008`.
## Prochaine tranche
`pre.008` : unification replay/redondance/discovery HTTP/hydration dans le ledger, calcul du continuity frontier et remplacement de la terminalité « first source failure » uniquement lorsqu'une `TargetCoverage` présente et future est explicitement prouvée ; aucun respawn Worker d'une source Transport.

View File

@@ -0,0 +1,172 @@
<!-- file: deltas/0.3.14/pre.008-fix.001.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.008-fix.001` — libération du guard avant `await`
## Base requise
```text
0.3.14-pre.008
workspace.package.version = 0.3.14-pre.8
deltas/0.3.14/pre.008.md présent
```
## Objectif
Corriger strictement la tranche `pre.008` après le gate opérateur, sans modifier sa responsabilité fonctionnelle :
```text
rendre le future du supervisor Send
ne jamais conserver un std::sync::MutexGuard à travers un await
préserver exactement la décision source-loss de pre.008
ne pas avancer la health policy prévue en pre.009
```
## Défaut observé
Le gate opérateur de `0.3.14-pre.008` a produit :
```text
cargo fmt --all : PASS
cargo fmt --all -- --check : PASS
audit Rust workspace rules : PASS
audit Markdown tables : PASS
cargo check --workspace : FAIL
cargo clippy --workspace --all-targets --all-features -- -D warnings : FAIL
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features : compilation impossible sur le même défaut Send
```
Le compilateur refuse le future de `supervise_live_source_tasks` :
```text
future cannot be sent between threads safely
std::sync::MutexGuard<RawTransactionIngestSourceInventory> is not Send
await dans drain_live_source_tasks alors que le guard peut encore être vivant
```
## Cause
`pre.008` appelait `inventory.supervisor_state()` dans un bloc qui contenait aussi la branche d'erreur avec `drain_live_source_tasks(...).await`.
Même si le guard n'est pas utilisé après `supervisor_state()`, sa portée lexicale englobait la branche asynchrone. Le compilateur devait donc considérer que le `std::sync::MutexGuard` pouvait traverser l'`await`, ce qui rendait le future non-`Send` et interdisait son utilisation dans `JoinSet::spawn` et `tokio::spawn`.
## Correction
La lecture synchronisée de l'inventaire est séparée en deux étapes :
```text
1. verrouiller inventory et calculer supervisor_state() dans un bloc strictement synchrone
2. sortir de ce bloc, donc détruire le MutexGuard
3. seulement ensuite traiter Result et éventuellement await drain_live_source_tasks(...)
```
Aucun `tokio::Mutex` n'est introduit. Aucun lock n'est conservé pendant une attente asynchrone.
La valeur, les erreurs et les décisions issues de `supervisor_state()` restent identiques.
## Fichiers ajoutés
```text
deltas/0.3.14/pre.008-fix.001.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
```
## Fichiers supprimés
```text
aucun
```
## Version Cargo
Le fix modifie du code Rust ; conformément à `VER-ID-007` et `VER-ID-010` :
```text
header Cargo.toml : 576 -> 577
workspace.package.version : 0.3.14-pre.8 -> 0.3.14-pre.8.fix.1
```
Version de fichier :
```text
src/runtime_resources.rs : 35 -> 36
```
## Frontières préservées
```text
aucun changement de TargetCoverage
aucun changement de continuity frontier
aucun changement de coverage epoch
aucun changement de gap ledger
aucun changement de source-loss classification
aucun respawn Worker
aucun nouveau scheduler ou task
aucun changement Transport
aucune nouvelle requête HTTP
aucune nouvelle dépendance ou feature
aucun accès Config
aucun Job Backfill
aucun backend Store physique
aucune croissance de surface publique
```
## Validations exécutées
Dans le sandbox de préparation :
```text
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
inspection de la portée du MutexGuard autour de supervise_live_source_tasks
comparaison exacte pre.008 -> pre.008-fix.001
contrôle du contenu de l'archive delta
unzip -t de l'archive delta
```
## Validations non exécutées
Le sandbox de préparation ne fournit pas le toolchain Cargo/Rust. Les gates suivants restent à exécuter côté 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-worker-raw-transaction-ingest-lib --all-targets --all-features
```
## Décisions prises
```text
fix strict de pre.008
réduction de portée lexicale du std::sync::MutexGuard
aucun passage à tokio::Mutex
aucun allow/expect lint
aucune modification fonctionnelle de la réconciliation
pre.009 reste la première tranche autorisée à modifier la health policy multi-source
```
## Questions ouvertes
```text
aucune pour ce fix
```
## Gate opérateur après application
```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-worker-raw-transaction-ingest-lib --all-targets --all-features
```

View File

@@ -0,0 +1,165 @@
<!-- file: deltas/0.3.14/pre.008-fix.002.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.008-fix.002` — suppression de l'auto-deref explicite du test
## Base requise
```text
0.3.14-pre.008-fix.001
workspace.package.version = 0.3.14-pre.8.fix.1
deltas/0.3.14/pre.008-fix.001.md présent
```
## Objectif
Corriger strictement le dernier échec Clippy observé après `pre.008-fix.001`, sans modifier le code production ni la responsabilité fonctionnelle de `pre.008` :
```text
supprimer clippy::explicit-auto-deref dans le helper de test supervisor_contracts
préserver exactement les fixtures et assertions de pre.008
ne pas avancer la health policy prévue en pre.009
```
## Défaut observé
Le gate opérateur de `0.3.14-pre.008-fix.001` a produit :
```text
cargo fmt --all : PASS
cargo fmt --all -- --check : PASS
audit Rust workspace rules : PASS
audit Markdown tables : PASS
cargo check --workspace : PASS
cargo clippy --workspace --all-targets --all-features -- -D warnings : FAIL
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features : PASS, 139 unit tests et toutes les suites d'intégration
```
Clippy signale dans `unit_tests/runtime_resources.rs` :
```text
explicit-auto-deref
exact_source_scope(*family, ...)
help: use family
```
## Cause
Dans la boucle sur `source_specs`, `family` est déjà une référence compatible avec le paramètre de `exact_source_scope`. Le déréférencement explicite `*family` est donc redondant et déclenche `clippy::explicit-auto-deref` sous `-D warnings`.
## Correction
Le helper de test utilise désormais directement `family` :
```text
exact_source_scope(family, [*fingerprint_byte; 32])
```
Aucun type production, aucune donnée de fixture et aucune décision de continuité ne changent.
## Fichiers ajoutés
```text
deltas/0.3.14/pre.008-fix.002.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
```
## Fichiers supprimés
```text
aucun
```
## Version Cargo
Le fix modifie un fichier Rust de test ; conformément à `VER-ID-007` et `VER-ID-010` :
```text
header Cargo.toml : 577 -> 578
workspace.package.version : 0.3.14-pre.8.fix.1 -> 0.3.14-pre.8.fix.2
```
Version de fichier :
```text
unit_tests/runtime_resources.rs : 29 -> 30
```
## Frontières préservées
```text
aucun changement du code production
aucun changement de TargetCoverage
aucun changement de continuity frontier
aucun changement de coverage epoch
aucun changement de gap ledger
aucun changement de source-loss classification
aucun respawn Worker
aucun nouveau scheduler ou task
aucun changement Transport
aucune nouvelle requête HTTP
aucune nouvelle dépendance ou feature
aucun accès Config
aucun Job Backfill
aucun backend Store physique
aucune croissance de surface publique
```
## Validations exécutées
Dans le sandbox de préparation :
```text
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
inspection ciblée du helper supervisor_contracts
comparaison exacte pre.008-fix.001 -> pre.008-fix.002
contrôle du contenu de l'archive delta
unzip -t de l'archive delta
```
## Validations non exécutées
Le sandbox de préparation ne fournit pas le toolchain Cargo/Rust. Les gates suivants restent à exécuter côté 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-worker-raw-transaction-ingest-lib --all-targets --all-features
```
## Décisions prises
```text
fix strict de pre.008
aucun allow/expect lint
aucune modification du code production
aucune modification fonctionnelle de la réconciliation
pre.009 reste la première tranche autorisée à modifier la health policy multi-source
```
## Questions ouvertes
```text
aucune pour ce fix
```
## Gate opérateur après application
```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-worker-raw-transaction-ingest-lib --all-targets --all-features
```

285
deltas/0.3.14/pre.008.md Normal file
View File

@@ -0,0 +1,285 @@
<!-- file: deltas/0.3.14/pre.008.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.008` — réconciliation et supervisor source-loss
## Base requise
```text
0.3.14-pre.007-fix.001
workspace.package.version = 0.3.14-pre.7.fix.1
deltas/0.3.14/pre.007-fix.001.md présent
```
## Gate de la base
Le gate opérateur de `0.3.14-pre.007-fix.001` est validé avant ouverture de cette tranche :
```text
cargo fmt --all : PASS
cargo fmt --all -- --check : PASS
audit Rust workspace rules : PASS
audit Markdown tables : PASS
cargo check --workspace : PASS
cargo clippy --workspace --all-targets --all-features -- -D warnings : PASS
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features : PASS
```
Le gate Worker comprend notamment :
```text
133 unit tests : PASS
cross_layer_completeness : 8 PASS
dependency_boundary : 19 PASS
hardening : 32 PASS
public_api : 20 PASS
release_completeness : 9 PASS
```
## Objectif
Implémenter strictement la tranche `pre.008` du plan `035` :
```text
unifier les preuves replay/redondance/discovery HTTP/hydration autour du ledger de continuité
maintenir un continuity frontier distinct du processing frontier
conserver les Missing known-reference comme gaps single-slot après libération du processing frontier
réconcilier les gaps uniquement depuis des coverage epochs réellement prouvés
remplacer la terminalité « first source failure » uniquement lorsqu'une TargetCoverage présente et future est explicitement prouvée
ne jamais respawn une source Transport depuis le Worker
rester fail-closed lorsque la plage de perte est non bornée ou qu'une preuve manque
```
## Continuity frontier distinct du processing frontier
Le gap ledger calcule désormais un `continuity_frontier` à partir du processing frontier courant :
```text
aucun gap ouvert <= processing frontier
-> continuity frontier = processing frontier
gap ouvert commençant à S <= processing frontier
-> continuity frontier = S - 1
gap ouvert commençant à 0
-> aucun continuity frontier
```
Le processing frontier reste donc un indicateur de traitement et ne peut jamais, à lui seul, fermer une discontinuité historique.
## Coverage epochs réellement prouvés
Le ledger `pre.005` reçoit désormais les epochs produits pendant l'exécution.
La source HTTP Block Polling full-ledger enregistre un epoch uniquement lorsqu'une fenêtre bornée de `pre.006` a été effectivement prouvée :
```text
fenêtre discovery bornée
+ tous les blocs produits de la partie prouvée effectivement matérialisés
+ aucun getBlock observed = null bloquant la fenêtre
= coverage epoch admissible
```
Un epoch adjacent ou chevauchant du même source/scope/commitment est fusionné. L'enregistrement d'un nouvel epoch déclenche immédiatement une tentative de réconciliation des gaps déjà ouverts.
Aucune source filtrée n'obtient un epoch par sa simple présence ou sa configuration.
## Missing known-reference raccordé au ledger
Le résultat `Missing` introduit en `pre.007` devient un gap de continuité single-slot :
```text
source_key exact
commitment exact
scope configuré exact de la source
range = [slot, slot]
reason = KnownReferenceMissing
```
Le pending de processing peut ensuite être soldé normalement. Le gap reste toutefois ouvert dans le ledger jusqu'à une preuve de coverage distincte.
Un epoch full-ledger d'une autre source couvrant ce slot peut donc réconcilier cette obligation sans inventer une observation de transaction et sans assimiler `getTransaction = null` à une absence prouvée.
Aucun second registre d'hydration, scheduler, semaphore ou pipeline n'est ajouté.
## Source-loss bornée et supervisor
Les tâches source supervisées retournent désormais leur `source_key` avec leur résultat terminal. Les projections source existantes restent l'autorité pour déterminer les sources réellement `Active` au moment de la décision.
Les incidents WS déjà bornés en `pre.003` transportent leurs bornes inclusives jusqu'au supervisor. Une perte classifiée et bornée est enregistrée comme gap selon le scope/commitment exacts de la source perdue.
Le supervisor peut continuer sans cette source uniquement si, après réconciliation :
```text
aucun gap connu ne reste ouvert
continuity frontier == processing frontier
chaque requirement de TargetCoverage est couvert par au moins une source encore Active
la source perdue n'est pas utilisée comme source active de remplacement
les relations de scope restent celles de pre.005 : Exact ou Superset explicitement démontré
le commitment reste strictement identique
```
Sinon, la politique précédente reste fail-closed et les sources sœurs sont arrêtées puis jointes comme auparavant.
## Pertes qui restent terminales
Cette tranche ne fabrique aucune plage lorsqu'elle n'est pas connue.
Restent donc terminales, entre autres :
```text
source transport perdue sans borne de continuité sûre
replay Yellowstone dont la coverage reste non prouvée sans range sûr
fermeture configurée sans range historique démontré
join failure / task-set invariant
content conflict
persistence failure
counter/invariant exhaustion
```
Le simple fait qu'une autre source soit configurée ou `Active` ne constitue jamais une preuve historique de l'intervalle perdu.
## Aucun respawn Worker
Le Worker ne recrée aucune source terminale. Transport conserve l'ownership de ses propres reconnect/replay mechanisms.
Après une décision `Continue`, le supervisor conserve uniquement les tâches sœurs déjà vivantes. La source perdue reste absente du run.
Les frontières historiques restent inchangées :
```text
aucun set_from_slot dans Worker
aucun SubscribeReplayInfo dans Worker
aucun socket WebSocket/gRPC créé par Worker
aucun retry loop réseau Worker
aucun accès Config
aucun Job Backfill
aucun backend Store physique
```
## Tests ajoutés
Les unit tests couvrent notamment :
```text
continuity frontier bloqué par un gap puis rétabli après coverage epoch redondant
source-loss refusée si les sources actives ne couvrent plus toute TargetCoverage
source-loss refusée tant qu'un gap reste ouvert malgré une source future-capable
source HTTP full-ledger prouvée permettant la survie d'une source filtrée perdue sans respawn
seules les pertes classifiées entrent dans la décision de coverage
Missing known-reference transféré au continuity ledger sans bloquer le processing frontier
Missing single-slot réconcilié seulement après epoch full-ledger réellement prouvé par une autre source
```
Les canaris `hardening` et `release_completeness` vérifient en plus :
```text
présence du continuity frontier privé
présence du gate TargetCoverage dans la décision source-loss
présence de record_known_reference_gap sur le chemin runtime Missing
aucune croissance de surface publique
aucun respawn source Worker
aucun ownership replay Transport déplacé vers Worker
```
## Hors périmètre inchangé
```text
aucune health policy Healthy/Degraded/Unhealthy/Faulted de pre.009
aucune fairness spécifique pendant réconciliation de pre.010
aucune observabilité publique détaillée des gaps de pre.011
aucun mécanisme EARLY/shred
aucun backfill historique caller-driven
aucune nouvelle source/provider
```
## Fichiers ajoutés
```text
deltas/0.3.14/pre.008.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/continuity.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/continuity.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
```
## Fichiers supprimés
```text
aucun
```
## Version Cargo
Conformément à `VER-ID-009` :
```text
header Cargo.toml : 575 -> 576
workspace.package.version : 0.3.14-pre.7.fix.1 -> 0.3.14-pre.8
```
Versions des fichiers modifiés :
```text
continuity.rs : 7 -> 8
lib.rs : 31 -> 32
runtime_resources.rs : 34 -> 35
unit_tests/continuity.rs : 5 -> 6
unit_tests/runtime_resources.rs : 28 -> 29
tests/hardening.rs : 31 -> 32
tests/release_completeness.rs : 26 -> 27
```
## Validation exécutée dans l'environnement de préparation
```text
python3 scripts/audit_rust_workspace_rules.py : PASS
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas : PASS
scan des frontières Worker/Transport/Backfill/Store : PASS
scan de la crate-root historique : PASS
comparaison exacte pre.007-fix.001 -> pre.008 : PASS
```
Les gates Cargo ne sont pas déclarés PASS dans l'environnement de préparation lorsqu'ils ne peuvent pas y être exécutés. Ils restent obligatoires côté opérateur avant `pre.009`.
## Décisions prises
```text
une source active n'est pas une preuve historique de coverage
les coverage epochs doivent provenir d'une observation réellement prouvée pendant le run
le processing frontier peut avancer avec un Missing, mais le continuity frontier reste bloqué par son gap
la continuation source-loss exige à la fois réconciliation historique et TargetCoverage future par les sources encore Active
les pertes sans range sûr restent fail-closed
aucun respawn Worker
```
## Questions ouvertes
```text
aucune pour cette tranche
```
## Gate opérateur après application
```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-worker-raw-transaction-ingest-lib --all-targets --all-features
```
## Prochaine tranche
`pre.009` : appliquer la health policy multi-source `Healthy / Degraded / Unhealthy / Faulted` selon coverage présente et future, avec retour à Healthy seulement après source active et gaps fermés.

View File

@@ -0,0 +1,178 @@
<!-- file: deltas/0.3.14/pre.009-fix.001.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.009-fix.001` — rétablissement des canaris de health multi-source
## Base requise
```text
0.3.14-pre.009
workspace.package.version = 0.3.14-pre.9
deltas/0.3.14/pre.009.md présent
```
## Objectif
Corriger strictement les deux échecs de `tests/hardening.rs` observés après `pre.009`, sans modifier la politique fonctionnelle de health :
```text
rendre explicite le garde source_active < source_total attendu par le canari historique pre.010
rendre explicite le pont aggregate.with_continuity_health attendu par le canari pre.009
préserver exactement Healthy / Degraded / Unhealthy / Faulted et les preuves de continuité de pre.009
```
## Défaut observé
Le gate opérateur de `0.3.14-pre.009` a produit :
```text
cargo fmt --all : PASS
cargo fmt --all -- --check : PASS
audit Rust workspace rules : PASS
audit Markdown tables : PASS
cargo check --workspace : PASS
cargo clippy --workspace --all-targets --all-features -- -D warnings : PASS
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features : FAIL
```
Les suites précédant `hardening` passent, dont `143` unit tests. Deux canaris textuels échouent :
```text
v0_3_13_pre_010_multi_source_health_is_conservative_counted_and_redacted
required pre.010 health guard missing: source_active < source_total
v0_3_14_pre_009_health_policy_is_present_future_coverage_gated_and_source_neutral
required pre.009 inventory-health bridge missing: aggregate.with_continuity_health
```
## Cause
Les deux invariants sont présents sémantiquement mais ne sont plus visibles sous les formes textuelles exactes attendues par les canaris :
```text
snapshot.source_active < snapshot.source_total
aggregate\n .with_continuity_health(...)
```
Le hardening recherche volontairement les formes explicites historiques afin de protéger les invariants de health et le pont Inventory -> Continuity -> Snapshot.
## Correction
`health_for_state` matérialise des variables locales source-neutres :
```text
source_total
source_active
source_failed
```
La condition `Degraded` exprime donc explicitement :
```text
source_active < source_total
```
Le pont de projection matérialise ensuite :
```text
let aggregate = aggregate.with_continuity_health(...)
```
avant d'ajouter `failed_source_losses_reconciled`.
Ces changements sont algébriquement équivalents au code de `pre.009` et ne changent aucune décision runtime.
## Fichiers ajoutés
```text
deltas/0.3.14/pre.009-fix.001.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/snapshot.rs
```
## Fichiers supprimés
```text
aucun
```
## Version Cargo
Le correctif modifie du code Rust production ; conformément à `VER-ID-007` et `VER-ID-010` :
```text
header Cargo.toml : 579 -> 580
workspace.package.version : 0.3.14-pre.9 -> 0.3.14-pre.9.fix.1
```
Versions de fichiers :
```text
src/runtime_resources.rs : 37 -> 38
src/snapshot.rs : 6 -> 7
```
## Frontières préservées
```text
aucune modification de TargetCoverage
aucune modification du gap ledger
aucune modification des coverage epochs
aucune modification du continuity frontier
aucune modification de la classification source-loss
aucun respawn Worker
aucun nouveau scheduler ou task
aucun changement Transport
aucune nouvelle requête HTTP
aucune nouvelle dépendance ou feature
aucun accès Config
aucun Job Backfill
aucun backend Store physique
aucune croissance de surface publique
```
## Validations exécutées
Dans le sandbox de préparation :
```text
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
vérification ciblée des deux tokens de hardening
contrôle du diff exact
contrôle d'inventaire ZIP
unzip -t
réapplication du delta sur la base pre.009 et comparaison byte-à-byte
```
## Validations non exécutées
Le toolchain Rust/Cargo n'est pas disponible dans le sandbox de préparation. Le gate opérateur reste requis :
```text
cargo fmt --all
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features
```
## Décisions prises
```text
corriger les formes explicites attendues plutôt que relâcher ou supprimer les canaris
ne pas ajouter de #[allow(...)]
ne pas modifier la sémantique de health validée par les unit tests pre.009
```
## Questions ouvertes
```text
aucune pour ce correctif
```

View File

@@ -0,0 +1,140 @@
<!-- file: deltas/0.3.14/pre.009-fix.002.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.009-fix.002` — garde Healthy explicite du snapshot
## Base requise
```text
0.3.14-pre.009-fix.001
workspace.package.version = 0.3.14-pre.9.fix.1
deltas/0.3.14/pre.009-fix.001.md présent
```
## Objectif
Corriger strictement le dernier canari `pre.009` en échec après `pre.009-fix.001`, sans modifier la politique fonctionnelle de health.
## Défaut observé
Le gate opérateur de `0.3.14-pre.009-fix.001` valide les audits, `cargo check`, Clippy et les `143` unit tests, puis échoue sur un seul canari :
```text
v0_3_14_pre_009_health_policy_is_present_future_coverage_gated_and_source_neutral
required pre.009 health projection guard missing: snapshot.source_active == snapshot.source_total
```
## Cause
`pre.009-fix.001` a conservé exactement l'invariant mais l'a exprimé via les variables locales :
```text
source_active == source_total
```
Le canari protège volontairement la forme explicite sur le snapshot :
```text
snapshot.source_active == snapshot.source_total
```
## Correction
La branche `Healthy` de `health_for_state` utilise directement la condition protégée :
```text
snapshot.source_active == snapshot.source_total
```
Les variables locales `source_active`, `source_total` et `source_failed` restent utilisées par la branche `Degraded`. La condition est strictement équivalente et n'introduit aucun changement runtime.
## Fichiers ajoutés
```text
deltas/0.3.14/pre.009-fix.002.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/snapshot.rs
```
## Fichiers supprimés
```text
aucun
```
## Version Cargo
Le correctif modifie du code Rust production ; conformément aux règles de versioning :
```text
header Cargo.toml : 580 -> 581
workspace.package.version : 0.3.14-pre.9.fix.1 -> 0.3.14-pre.9.fix.2
```
Version de fichier :
```text
src/snapshot.rs : 7 -> 8
```
## Frontières préservées
```text
aucune modification de TargetCoverage
aucune modification du gap ledger
aucune modification des coverage epochs
aucune modification du continuity frontier
aucune modification de la classification source-loss
aucun respawn Worker
aucun nouveau scheduler ou task
aucun changement Transport
aucune nouvelle requête HTTP
aucune nouvelle dépendance ou feature
aucun accès Config
aucun Job Backfill
aucun backend Store physique
aucune croissance de surface publique
```
## Validations exécutées
```text
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
vérification ciblée du canari snapshot.source_active == snapshot.source_total
contrôle du diff exact
contrôle d'inventaire ZIP
unzip -t
réapplication du delta sur la base pre.009-fix.001 et comparaison byte-à-byte
```
## Validations non exécutées
Le toolchain Rust/Cargo n'est pas disponible dans le sandbox de préparation. Le gate opérateur reste requis :
```text
cargo fmt --all
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features
```
## Décisions prises
```text
corriger la forme explicite attendue plutôt que relâcher le canari
ne pas ajouter de #[allow(...)]
ne pas modifier la sémantique health validée par les unit tests pre.009
```
## Questions ouvertes
```text
aucune pour ce correctif
```

270
deltas/0.3.14/pre.009.md Normal file
View File

@@ -0,0 +1,270 @@
<!-- file: deltas/0.3.14/pre.009.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.009` — health policy multi-source
## Base requise
```text
0.3.14-pre.008-fix.002
workspace.package.version = 0.3.14-pre.8.fix.2
deltas/0.3.14/pre.008-fix.002.md présent
```
## Gate de la base
Le gate opérateur de `0.3.14-pre.008-fix.002` est validé avant ouverture de cette tranche :
```text
cargo fmt --all : PASS
cargo fmt --all -- --check : PASS
audit Rust workspace rules : PASS
audit Markdown tables : PASS
cargo check --workspace : PASS
cargo clippy --workspace --all-targets --all-features -- -D warnings : PASS
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features : PASS
```
Le gate Worker comprend notamment :
```text
139 unit tests : PASS
cross_layer_completeness : 8 PASS
dependency_boundary : 19 PASS
hardening : 33 PASS
public_api : 20 PASS
release_completeness : 10 PASS
```
## Objectif
Implémenter strictement la tranche `pre.009` du plan `035` :
```text
projeter Healthy / Degraded / Unhealthy depuis des preuves de coverage présentes et futures
conserver Faulted comme état lifecycle terminal du Worker
ne jamais rendre Healthy sur le seul fait qu'un Transport ait reconnecté
rendre Healthy uniquement lorsque toutes les sources attendues sont Active et que les gaps sont fermés
rendre Degraded lorsqu'une source reste perdue mais que le présent est réconcilié et TargetCoverage reste couvert pour la suite
rendre Unhealthy pendant une réconciliation non prouvée, avec gap ouvert ou coverage future insuffisante
ne créer aucune health source/provider spécifique publique
```
## Projection de health fondée sur la continuité
Le ledger de `pre.008` expose maintenant une projection privée et source-neutral :
```text
continuity_frontier
has_open_gaps
future_target_coverage
failed_source_losses_reconciled
```
Pour chaque source projetée `Failed`, `failed_source_losses_reconciled` exige un gap `SourceFailure` effectivement enregistré puis fermé par une preuve run-local. Une source simplement marquée `Failed` dans l'inventaire ne suffit donc jamais à autoriser `Degraded`.
`future_target_coverage` est calculé uniquement depuis les sources réellement `Active` et les relations `Exact` / `Superset` déjà validées par `TargetCoverage`.
La configuration d'une source ne constitue toujours pas une preuve historique. Le présent reste gouverné par :
```text
aucun gap ouvert
continuity frontier == processing frontier
```
Les deux dimensions sont volontairement distinctes :
```text
coverage future disponible != gap historique réparé
gap historique réparé != coverage future encore disponible
```
## Pont Inventory -> Continuity -> Snapshot
Chaque publication agrégée de l'inventaire multi-source calcule désormais, après libération du lock Inventory :
```text
ensemble exact des sources Active
processing frontier agrégé
projection continuity/TargetCoverage sous le lock continuity séparé
projection health privée ajoutée au message latest-value
```
Aucun `MutexGuard` n'est conservé à travers un `await` et aucun client Transport/Store n'est exposé dans la snapshot publique.
La source HTTP full-ledger republie sa projection après l'enregistrement d'un coverage epoch afin qu'une fermeture de gap puisse mettre à jour la health sans attendre une transaction ou un slot produit supplémentaire.
## Politique Running
Lorsque la projection de continuité est disponible, la health suit la politique conservative suivante :
```text
source Reconnecting
-> Unhealthy tant que la continuité de l'incident n'est pas explicitement réconciliée
gap ouvert
ou continuity frontier != processing frontier
ou TargetCoverage future non couverte par les sources Active
-> Unhealthy
toutes les sources attendues Active
+ aucun gap ouvert
+ continuity frontier rattrapé
+ TargetCoverage future couverte
-> Healthy
une source reste perdue/Failed
+ son gap SourceFailure a été enregistré et réconcilié
+ aucune source Reconnecting
+ aucun gap ouvert
+ continuity frontier rattrapé
+ TargetCoverage future couverte
-> Degraded
```
Une source perdue n'est donc jamais transformée en source optionnelle. `Degraded` indique uniquement que le service du run reste couvert malgré son absence ; les observations spécifiques que cette source aurait produites ne sont pas inventées.
## Faulted reste un état lifecycle
`ksp-worker-api::WorkerHealth` reste inchangé et conserve :
```text
Unknown
Healthy
Degraded
Unhealthy
```
La quatrième politique terminale du plan est représentée par :
```text
WorkerState::Faulted(error_code)
WorkerHealth::Unhealthy
```
Aucune nouvelle variante `WorkerHealth::Faulted` ni extension de l'API générique Worker n'est introduite.
## Retour à Healthy
Le retour à `Healthy` exige simultanément :
```text
toutes les sources attendues projetées Active
aucune source projetée Reconnecting
aucun gap run-local ouvert
continuity frontier == processing frontier
TargetCoverage future couverte par les sources Active
```
Le simple passage Transport `Reconnecting -> Active` ne constitue pas une preuve suffisante si le continuity ledger n'est pas réconcilié.
## Tests ajoutés
Les unit tests couvrent notamment :
```text
projection health séparant future TargetCoverage et gap historique ouvert
rejet des ensembles Active dupliqués ou inconnus
pont Inventory -> continuity health avec gap puis coverage epoch de réconciliation
Reconnecting -> Unhealthy tant que la preuve de continuité manque
sources Active + gap ouvert -> Unhealthy
sources Active + gaps fermés + coverage future -> Healthy
source Failed sans gap SourceFailure réconcilié -> Unhealthy
source Failed avec gap SourceFailure réconcilié et présent/futur couverts -> Degraded
publication supervisor immédiate après réconciliation réussie de la perte de source
coverage future insuffisante -> Unhealthy
WorkerState::Faulted -> WorkerHealth::Unhealthy
```
Les canaris `hardening` et `release_completeness` vérifient en plus :
```text
health fondée sur continuity frontier + gaps + TargetCoverage
aucune health provider/source-key spécifique publique
aucune variante WorkerHealth::Faulted ajoutée
aucune croissance de la surface publique TargetCoverage
```
## Hors périmètre inchangé
```text
aucune fairness spécifique nominal/repair de pre.010
aucune observabilité publique détaillée des gaps/repair de pre.011
aucun nouveau mécanisme de replay/reconnect Worker
aucun respawn de source
aucun EARLY/shred
aucun Job Backfill depuis Worker
aucun nouveau provider ou SDK
```
## Fichiers ajoutés
```text
deltas/0.3.14/pre.009.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/continuity.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/snapshot.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/continuity.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/snapshot.rs
```
## Fichiers supprimés
```text
aucun
```
## Version Cargo
Conformément à `VER-ID-009` :
```text
header Cargo.toml : 578 -> 579
workspace.package.version : 0.3.14-pre.8.fix.2 -> 0.3.14-pre.9
```
Versions des fichiers modifiés :
```text
continuity.rs : 8 -> 9
runtime_resources.rs : 36 -> 37
snapshot.rs : 5 -> 6
unit_tests/continuity.rs : 6 -> 7
unit_tests/runtime_resources.rs : 30 -> 31
unit_tests/snapshot.rs : 5 -> 6
tests/hardening.rs : 32 -> 33
tests/release_completeness.rs : 27 -> 28
```
## Validation exécutée dans l'environnement de préparation
```text
python3 scripts/audit_rust_workspace_rules.py : PASS
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas : PASS
scan des frontières Worker/Transport/Backfill/Store : PASS
scan de la crate-root historique : PASS
comparaison exacte pre.008-fix.002 -> pre.009 : PASS
```
Les gates Cargo ne sont pas déclarés PASS dans l'environnement de préparation lorsqu'ils ne peuvent pas y être exécutés. Ils restent obligatoires côté opérateur avant `pre.010`.
## Décisions prises
```text
la health est une projection de preuves run-local, pas un statut provider
le futur est prouvé par TargetCoverage sur les sources Active
le présent est prouvé par continuity frontier + absence de gap ouvert
Reconnecting reste Unhealthy tant que l'incident n'est pas réconcilié
Failed ne devient Degraded qu'après enregistrement et fermeture effective de son gap SourceFailure
une source perdue mais réellement redondante maintient Degraded, jamais Healthy
Faulted reste un WorkerState terminal avec WorkerHealth::Unhealthy
```

View File

@@ -0,0 +1,144 @@
<!-- file: deltas/0.3.14/pre.010-fix.001.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.010-fix.001` — queue de fairness explicitement bornée
## Base requise
```text
0.3.14-pre.010
workspace.package.version = 0.3.14-pre.10
deltas/0.3.14/pre.010.md présent
```
## Objectif
Corriger strictement le canari historique `pre.009` en échec après `pre.010`, sans modifier l'algorithme de fairness nominal / repair.
## Défaut observé
Le gate opérateur de `0.3.14-pre.010` valide les audits, `cargo check`, Clippy et les `147` unit tests, puis échoue sur un seul canari :
```text
v0_3_13_pre_009_duplicate_storm_disagreement_and_starvation_guards_are_explicit
pre.009 introduced forbidden unbounded/provider-preference behavior: VecDeque::new()
```
## Cause
`pre.010` a introduit la queue privée de waiters de fairness avec :
```text
VecDeque::new()
```
La queue était déjà bornée fonctionnellement par :
```text
gate.max_waiters == 0 || state.waiters.len() >= gate.max_waiters
```
mais le canari historique protège également l'absence de construction textuellement non bornée.
## Correction
La construction devient explicitement dimensionnée avec la borne existante :
```text
VecDeque::with_capacity(max_waiters)
```
La garde `state.waiters.len() >= gate.max_waiters` reste inchangée et demeure l'autorité qui interdit toute croissance au-delà de la borne.
Aucune capacité supplémentaire n'est réservée fonctionnellement, aucun waiter n'est admis en plus et l'alternance `Nominal / Repair` est inchangée.
## Fichiers ajoutés
```text
deltas/0.3.14/pre.010-fix.001.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
```
## Fichiers supprimés
```text
aucun
```
## Version Cargo
Le correctif modifie du code Rust production ; conformément aux règles de versioning :
```text
header Cargo.toml : 582 -> 583
workspace.package.version : 0.3.14-pre.10 -> 0.3.14-pre.10.fix.1
```
Version de fichier :
```text
src/runtime_resources.rs : 39 -> 40
```
## Frontières préservées
```text
aucune seconde queue d'admission ou de persistence
aucun second registre d'hydration
aucune nouvelle semaphore
aucune capacité repair réservée
burst repair = 1 inchangé
fanout bloc repair <= 4 inchangé
fenêtre discovery <= 512 inchangée
aucun changement de TargetCoverage ou continuity ledger
aucun changement de health policy
aucun respawn Worker
aucun changement Transport
aucun Job Backfill
aucun backend Store physique
aucune croissance de surface publique
```
## Validations exécutées
```text
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
vérification ciblée de l'absence de VecDeque::new() dans le code Worker contrôlé par le canari
contrôle du diff exact
contrôle d'inventaire ZIP
unzip -t
réapplication du delta sur la base pre.010 et comparaison byte-à-byte
```
## Validations non exécutées
Le toolchain Rust/Cargo n'est pas disponible dans le sandbox de préparation. Le gate opérateur reste requis :
```text
cargo fmt --all
cargo fmt --all -- --check
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features
```
## Décisions prises
```text
rendre la borne existante explicite à la construction plutôt que relâcher le canari
ne pas ajouter de #[allow(...)]
ne pas modifier la fairness, les quotas ni les pipelines existants
```
## Questions ouvertes
```text
aucune pour ce correctif
```

273
deltas/0.3.14/pre.010.md Normal file
View File

@@ -0,0 +1,273 @@
<!-- file: deltas/0.3.14/pre.010.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.010` — backpressure et fairness nominal / repair
## Base requise
```text
0.3.14-pre.009-fix.002
workspace.package.version = 0.3.14-pre.9.fix.2
deltas/0.3.14/pre.009-fix.002.md présent
```
## Gate de la base
Le gate opérateur de `0.3.14-pre.009-fix.002` est validé avant ouverture de cette tranche :
```text
cargo fmt --all : PASS
cargo fmt --all -- --check : PASS
audit Rust workspace rules : PASS
audit Markdown tables : PASS
cargo check --workspace : PASS
cargo clippy --workspace --all-targets --all-features -- -D warnings : PASS
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features : PASS
```
Le gate Worker comprend notamment :
```text
143 unit tests : PASS
cross_layer_completeness : 8 PASS
dependency_boundary : 19 PASS
hardening : 34 PASS
public_api : 20 PASS
release_completeness : 11 PASS
```
## Objectif
Implémenter strictement la tranche `pre.010` du plan `035` :
```text
conserver une admission Common RAW centrale unique
conserver un pipeline de persistence unique et ses limites existantes
faire partager au trafic nominal et repair le registre/coalescence global d'hydration existant
ordonner nominal/repair sans réserver une fraction fixe de capacité
permettre la progression lorsque la capacité existante vaut 1
borner le burst repair lorsqu'un nominal attend
conserver un fanout logique getBlock <= 4
conserver les fenêtres HTTP <= 512 slots
ne créer aucun second pool Store, aucune seconde queue d'admission et aucun second registre d'hydration
```
## Arbitre privé nominal / repair
`runtime_resources.rs` introduit un arbitre privé source-neutral :
```text
RawTransactionIngestTrafficClass
Nominal
Repair
RawTransactionIngestFairTurnGate
```
Les waiters reçoivent des tickets monotones checked. La queue d'attente est bornée par la capacité déjà attribuée au registre global d'hydration ; aucun waiter pool illimité n'est ajouté.
Lorsque les deux classes attendent :
```text
premier arbitrage -> Nominal
après Nominal -> Repair
après Repair -> Nominal
```
Le burst repair maximal en présence de nominal est donc :
```text
MAX_RAW_TRANSACTION_INGEST_REPAIR_BURST = 1
```
Si une seule classe attend, elle progresse sans attendre artificiellement l'autre classe. Le mécanisme ne réserve donc jamais une capacité inexistante.
## Hydration globale partagée
Le `RawTransactionIngestGlobalHydrationRegistry` reste unique.
Le même sémaphore :
```text
hydration_permits
```
est maintenant précédé par l'arbitre nominal/repair. Le chemin live existant acquiert explicitement :
```text
RawTransactionIngestTrafficClass::Nominal
```
La classe `Repair` utilise la même primitive d'acquisition et le même sémaphore ; aucun `RepairHydrationRegistry`, aucune seconde semaphore d'hydration et aucune seconde coalescence ne sont créés.
L'arbitre gouverne l'ordre d'accès au permit ; le permit lui-même reste détenu pendant l'I/O comme auparavant. Les bornes globales existantes restent donc l'autorité de concurrence.
## Faible capacité et fanout HTTP
La validation run-local accepte explicitement :
```text
admission_queue_capacity = 1
persistence_concurrency = 1
```
Le fanout bloc repair dérive uniquement de la capacité existante :
```text
repair_block_fetch_limit(existing_capacity) = min(existing_capacity, 4)
```
Donc :
```text
capacité 1 -> fanout 1
capacité 4 -> fanout 4
capacité 64 -> fanout 4
```
Aucun worker ou permit n'est réservé à l'avance pour le repair. La borne historique reste également :
```text
MAX_RAW_TRANSACTION_INGEST_REPAIR_ACTIVE_GAPS = 1
MAX_RAW_TRANSACTION_INGEST_REPAIR_BLOCK_FETCH_IN_FLIGHT = 4
MAX_RAW_TRANSACTION_INGEST_REPAIR_DISCOVERY_WINDOW_SLOTS = 512
```
## Admission et persistence
Cette tranche ne crée aucune nouvelle `mpsc::channel` dans `runtime_resources.rs`.
Tout matériau Common RAW continue à utiliser :
```text
RawTransactionAdmission
la Sender centrale existante
le pipeline de persistence existant
la même persistence_concurrency
```
La fairness ajoutée ne contourne donc ni backpressure admission ni convergence/persistence Store.
## Compteurs et overflow
Les tickets de fairness utilisent `checked_add`. Un épuisement de ticket ou un dépassement de la queue de waiters produit une erreur Worker stable ; aucun `saturating_add` n'est utilisé pour masquer un overflow.
L'annulation d'un waiter retire son ticket de l'arbitre. La libération d'un turn réveille les waiters restants sans conserver de `std::sync::MutexGuard` à travers un `await`.
## Tests ajoutés
Les unit tests Worker couvrent :
```text
alternance Nominal -> Repair -> Nominal lorsque les deux classes attendent
burst repair borné à 1
queue de waiters bornée et libérée après annulation/drop
fanout bloc min(capacité, 4)
partage réel du même semaphore d'hydration entre Nominal et Repair
blocage Repair tant que le permit capacity=1 est occupé par Nominal
progression Repair seul avec capacity=1
```
Les canaris `hardening` et `release_completeness` vérifient en plus :
```text
une seule admission mpsc centrale
un seul RawTransactionIngestGlobalHydrationRegistry
absence de RepairHydrationRegistry
bornes 4 getBlock / 512 slots conservées
aucune fuite publique des classes ou de l'arbitre de fairness
```
## Hors périmètre inchangé
```text
aucune nouvelle snapshot publique de gap/repair avant pre.011
aucun changement de health policy pre.009
aucun nouveau mécanisme replay/reconnect Worker
aucun respawn de source
aucun EARLY/shred
aucun Job Backfill depuis Worker
aucun nouveau provider ou SDK
aucune nouvelle dépendance
```
## Fichiers ajoutés
```text
deltas/0.3.14/pre.010.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
```
## Fichiers supprimés
```text
aucun
```
## Version Cargo
Conformément à `VER-ID-009` :
```text
header Cargo.toml : 581 -> 582
workspace.package.version : 0.3.14-pre.9.fix.2 -> 0.3.14-pre.10
```
Versions des fichiers modifiés :
```text
runtime_resources.rs : 39 (bump depuis 38)
unit_tests/runtime_resources.rs : 32 (bump depuis 31)
tests/hardening.rs : 34 (bump depuis 33)
tests/release_completeness.rs : 29 (bump depuis 28)
```
## Validation exécutée dans l'environnement de préparation
```text
python3 scripts/audit_rust_workspace_rules.py : PASS
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas : PASS
scan des frontières Worker/Transport/Backfill/Store : PASS
scan de la crate-root historique : PASS
comparaison exacte pre.009-fix.002 -> pre.010 : PASS
```
Les gates Cargo ne sont pas déclarés PASS dans l'environnement de préparation lorsqu'ils ne peuvent pas y être exécutés. Ils restent obligatoires côté opérateur avant `pre.011`.
## Décisions prises
```text
fairness temporelle/alternée et non réservation de pourcentage
burst repair = 1 lorsque nominal attend
classe seule autorisée à progresser immédiatement
hydration nominal et repair sur le même registre + semaphore
fanout bloc dérivé de la capacité existante et plafonné à 4
aucune nouvelle queue admission/persistence
```
## Questions ouvertes
```text
aucune pour cette tranche
```
## Gate opérateur après application
```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-worker-raw-transaction-ingest-lib --all-targets --all-features
```

View File

@@ -0,0 +1,92 @@
<!-- file: deltas/0.3.14/pre.011-fix.001.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.011-fix.001` — annotation explicite du slot de gap le plus ancien
## Base requise
```text
0.3.14-pre.011
workspace.package.version = 0.3.14-pre.11
deltas/0.3.14/pre.011.md présent
```
## Défaut observé au gate opérateur
Les audits statiques de `pre.011` passent, mais `cargo check`, Clippy et les tests ne peuvent pas compiler le Worker à cause d'une inférence ambiguë :
```text
error[E0282]: type annotations needed for `Option<_>`
crates/ksp-worker-raw-transaction-ingest-lib/src/continuity.rs:476
```
La variable `oldest_open_gap_start_slot` est initialisée à `None` puis utilisée avec `u64::min`; Rust exige ici que le type de l'`Option` soit fixé explicitement.
## Correction
La variable devient explicitement :
```rust
let mut oldest_open_gap_start_slot: std::option::Option<u64> = std::option::Option::None;
```
Aucune logique de projection, aucun compteur, aucun état de gap et aucune méthode de réparation n'est modifié.
## Périmètre
Le fix reste strictement dans la responsabilité de `pre.011` :
```text
aucune modification de la surface publique
aucune modification de health policy
aucune modification de fairness
aucune modification de replay/reconnect ownership
aucune modification du pipeline Common RAW/Store
aucune nouvelle dépendance
```
## Fichiers ajoutés
```text
deltas/0.3.14/pre.011-fix.001.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/continuity.rs
```
## Fichiers supprimés
```text
aucun
```
## Version Cargo
Conformément à `VER-ID-007` et `VER-ID-010` :
```text
header Cargo.toml : 584 -> 585
workspace.package.version : 0.3.14-pre.11 -> 0.3.14-pre.11.fix.1
```
Version du fichier Rust modifié :
```text
src/continuity.rs : 10 -> 11
```
## Gate opérateur après application
```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-worker-raw-transaction-ingest-lib --all-targets --all-features
```

View File

@@ -0,0 +1,102 @@
<!-- file: deltas/0.3.14/pre.011-fix.002.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.011-fix.002` — restauration du pont textuel health/continuity
## Base requise
```text
0.3.14-pre.011.fix.1
workspace.package.version = 0.3.14-pre.11.fix.1
deltas/0.3.14/pre.011-fix.001.md présent
```
## Défaut observé au gate opérateur
Les audits, `cargo check`, Clippy et les `150` tests unitaires Worker passent après `pre.011-fix.001`, mais le test de hardening historique `v0_3_14_pre_009_health_policy_is_present_future_coverage_gated_and_source_neutral` échoue sur le canari textuel suivant :
```text
required pre.009 inventory-health bridge missing: aggregate.with_continuity_health
```
Le pont fonctionnel est toujours présent, mais `pre.011` l'a reformatté en chaîne de méthodes sur plusieurs lignes :
```rust
let aggregate = aggregate
.with_continuity_health(...)
.with_continuity_snapshot(...);
```
Le canari historique recherche volontairement la sous-chaîne continue `aggregate.with_continuity_health`.
## Correction
Le calcul est matérialisé en deux étapes strictement équivalentes :
```rust
let aggregate = aggregate.with_continuity_health(...);
let aggregate = aggregate.with_continuity_snapshot(...);
```
La sémantique runtime reste identique : la projection health/continuity est appliquée avant la projection détaillée des gaps, puis `with_failed_source_losses_reconciled(...)` reste l'étape finale.
## Périmètre
Le fix reste strictement dans la responsabilité de `pre.011` :
```text
aucune modification de la surface publique
aucune modification des compteurs de gaps
aucune modification de TargetCoverage
aucune modification de health policy
aucune modification de fairness
aucune modification de replay/reconnect ownership
aucune modification du pipeline Common RAW/Store
aucune nouvelle dépendance
```
## Fichiers ajoutés
```text
deltas/0.3.14/pre.011-fix.002.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
```
## Fichiers supprimés
```text
aucun
```
## Version Cargo
Conformément à `VER-ID-007` et `VER-ID-010` :
```text
header Cargo.toml : 585 -> 586
workspace.package.version : 0.3.14-pre.11.fix.1 -> 0.3.14-pre.11.fix.2
```
Version du fichier Rust modifié :
```text
src/runtime_resources.rs : 41 -> 42
```
## Gate opérateur après application
```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-worker-raw-transaction-ingest-lib --all-targets --all-features
```

310
deltas/0.3.14/pre.011.md Normal file
View File

@@ -0,0 +1,310 @@
<!-- file: deltas/0.3.14/pre.011.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.011` — snapshots / observabilité gap et récupération
## Base requise
```text
0.3.14-pre.010-fix.001
workspace.package.version = 0.3.14-pre.10.fix.1
deltas/0.3.14/pre.010-fix.001.md présent
```
## Gate de la base
Le gate opérateur de `0.3.14-pre.010-fix.001` est validé avant ouverture de cette tranche :
```text
cargo fmt --all : PASS
cargo fmt --all -- --check : PASS
audit Rust workspace rules : PASS
audit Markdown tables : PASS
cargo check --workspace : PASS
cargo clippy --workspace --all-targets --all-features -- -D warnings : PASS
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features : PASS
```
Le gate Worker comprend notamment :
```text
147 unit tests : PASS
cross_layer_completeness : 8 PASS
dependency_boundary : 19 PASS
hardening : 35 PASS
public_api : 20 PASS
release_completeness : 12 PASS
```
## Objectif
Implémenter strictement la tranche `pre.011` du plan `035` :
```text
stabiliser une projection publique source-neutral des gaps run-local
exposer gap_id, plage inclusive, état, raison et dernière méthode éventuelle
exposer les agrégats open/repairing/repaired/unresolved et méthodes de récupération
conserver les compteurs checked
borner la liste détaillée publiée
ne jamais exposer source key, provider, endpoint, signature, payload ou erreur distante arbitraire
ne modifier ni ownership Transport ni pipeline Common RAW/Store
```
## Types publics stabilisés
La surface `snapshot` ajoute :
```text
RawTransactionIngestGapId
RawTransactionIngestGapState
RawTransactionIngestGapReason
RawTransactionIngestRepairMethod
RawTransactionIngestGapSnapshot
```
`RawTransactionIngestGapSnapshot` expose uniquement :
```text
gap_id
start_slot
end_slot
state
reason
last_method éventuel
```
Aucune identité logique de source et aucune donnée provider n'est transportée.
## Raisons et états
Les raisons publiques reflètent le catalogue privé déjà validé :
```text
HttpProducedBlockUnavailable
KnownReferenceMissing
SourceFailure
TransportOverflow
WebSocketReconnect
YellowstoneRetention
```
Les états publics sont :
```text
Pending
Repairing
Repaired
Unresolved
```
Un gap `Unresolved` reste terminalement visible et n'est plus reclassé `Repaired` par l'arrivée tardive d'un coverage epoch.
## Méthodes source-neutral
Le catalogue public de méthode est :
```text
Replay
RedundantCoverage
HttpScan
BlockFetch
TransactionHydration
```
Dans le comportement productif actuellement fermé, la réconciliation par coverage epoch distincte enregistre explicitement :
```text
last_method = RedundantCoverage
```
Les autres méthodes restent à zéro tant qu'aucun mécanisme productif ne fournit une preuve correspondante ; la snapshot ne fabrique jamais une méthode depuis une simple configuration.
## Projection détaillée bornée
La liste publique `RawTransactionIngestSnapshot::gaps()` reste bornée par :
```text
MAX_RAW_TRANSACTION_INGEST_OPEN_REPAIR_GAPS = 64
```
La projection conserve toujours tous les gaps ouverts, puis utilise uniquement la capacité restante pour les gaps `Repaired` les plus récents. Les entrées sont triées par `gap_id` avant publication.
L'historique complet n'est donc jamais dupliqué dans la latest-value snapshot.
## Agrégats publics
`RawTransactionIngestSnapshot` expose désormais :
```text
open_gap_count
repairing_gap_count
repaired_gap_total
unresolved_gap_total
replay_repair_total
redundant_coverage_repair_total
http_scan_repair_total
repair_block_fetch_total
repair_transaction_hydration_total
oldest_open_gap_start_slot
```
Les comptes sont calculés avec `checked_add`. Un overflow devient une erreur Worker `counter_exhausted` ; aucune saturation silencieuse n'est introduite.
## Pont continuity -> snapshot
La projection privée `RawTransactionIngestContinuitySnapshotProjection` reste interne à la crate.
Le chemin de publication est :
```text
RawTransactionIngestGapLedger
-> RawTransactionIngestContinuityContracts::observability_projection
-> source_inventory_health_projection
-> RawTransactionIngestProcessingFrontierProjection
-> RawTransactionIngestSnapshotPublisher
-> RawTransactionIngestSnapshot
```
Le même `watch` latest-value existant est réutilisé. Aucun second canal public ou pipeline de persistence n'est créé.
## Non-Copy de la projection interne
La projection processing interne transporte maintenant un `Vec` borné de snapshots de gaps ; elle devient donc `Clone` mais n'est plus `Copy`.
Les points qui lisaient auparavant une valeur `watch` par déréférencement utilisent désormais `clone()` explicitement. Cela ne change ni les frontières d'ownership ni la sémantique latest-value.
## Tests ajoutés ou étendus
Les unit tests couvrent :
```text
gap Pending visible avec plage/raison exactes
gap réconcilié visible comme Repaired avec RedundantCoverage
gap Unresolved conservé et oldest_open_gap_start_slot exact
agrégats repaired/unresolved/méthodes exacts
propagation continuity -> inventory aggregate -> concrete snapshot
surface publique complète des getters gap/agrégats
borne détaillée <= 64
```
Les canaris `hardening`, `public_api` et `release_completeness` vérifient en plus :
```text
catalogue public exact attendu
compteurs checked
projection détaillée bornée
absence de source_key/endpoint/signature/payload dans snapshot.rs
absence de source_key dans la crate root
aucune croissance du graphe de dépendances ou du pipeline
```
## Hors périmètre inchangé
```text
aucun nouveau mécanisme de replay/reconnect Worker
aucun nouveau scan HTTP ou retry réseau
aucun changement de health policy pre.009
aucun changement de fairness pre.010
aucun respawn de source
aucun EARLY/shred
aucun Job Backfill depuis Worker
aucun backend Store physique
aucune nouvelle dépendance
shutdown/races réservés à pre.012
```
## Fichiers ajoutés
```text
deltas/0.3.14/pre.011.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/continuity.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/snapshot.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/public_api.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/continuity.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/snapshot.rs
```
## Fichiers supprimés
```text
aucun
```
## Version Cargo
Conformément à `VER-ID-009` :
```text
header Cargo.toml : 583 -> 584
workspace.package.version : 0.3.14-pre.10.fix.1 -> 0.3.14-pre.11
```
Versions des fichiers Rust modifiés :
```text
src/continuity.rs : 10
src/lib.rs : 33
src/runtime.rs : 18
src/runtime_resources.rs : 41
src/snapshot.rs : 9
unit_tests/continuity.rs : 8
unit_tests/runtime_resources.rs : 33
unit_tests/snapshot.rs : 7
tests/hardening.rs : 35
tests/public_api.rs : 23
tests/release_completeness.rs : 30
```
## Validation exécutée dans l'environnement de préparation
```text
python3 scripts/audit_rust_workspace_rules.py : PASS
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas : PASS
scan des frontières Worker/Transport/Backfill/Store : PASS
scan de redaction de la surface snapshot : PASS
contrôle de l'inventaire public attendu : PASS
comparaison exacte pre.010-fix.001 -> pre.011 : PASS
```
Les gates Cargo ne sont pas déclarés PASS dans l'environnement de préparation lorsqu'ils ne peuvent pas y être exécutés. Ils restent obligatoires côté opérateur avant `pre.012`.
## Décisions prises
```text
surface détaillée bornée à 64 entrées
priorité de projection à tous les gaps ouverts
historique réparé représenté par compteurs + capacité détaillée restante
Unresolved reste visible et non réconciliable tardivement
aucune identité de source publique
aucun nouveau canal latest-value
```
## Questions ouvertes
```text
aucune pour cette tranche
```
## Gate opérateur après application
```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-worker-raw-transaction-ingest-lib --all-targets --all-features
```

237
deltas/0.3.14/pre.012.md Normal file
View File

@@ -0,0 +1,237 @@
<!-- file: deltas/0.3.14/pre.012.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.012` — hardening races / shutdown
## Base requise
```text
0.3.14-pre.011-fix.002
workspace.package.version = 0.3.14-pre.11.fix.2
deltas/0.3.14/pre.011-fix.002.md présent
```
## Gate de la base
Le gate opérateur de `0.3.14-pre.011-fix.002` est validé avant ouverture de cette tranche :
```text
cargo fmt --all : PASS
cargo fmt --all -- --check : PASS
audit Rust workspace rules : PASS
audit Markdown tables : PASS
cargo check --workspace : PASS
cargo clippy --workspace --all-targets --all-features -- -D warnings : PASS
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features : PASS
```
Le gate Worker comprend notamment :
```text
150 unit tests : PASS
cross_layer_completeness : 8 PASS
dependency_boundary : 19 PASS
hardening : 36 PASS
public_api : 21 PASS
release_completeness : 13 PASS
```
## Objectif
Implémenter strictement la tranche `pre.012` du plan `035` :
```text
borner stop/fault pendant les sous-tâches source et les phases de continuité
préserver la première faute quand un sibling ne coopère pas au shutdown
abandonner et rejoindre les tâches source restantes après expiration de la deadline
empêcher une mutation de coverage après observation d'un stop HTTP
empêcher une hydration déjà jointe de poursuivre admission/continuity après observation d'un stop
conserver le drain/abort/join global déjà validé pour admission/persistence
ne créer aucun task pool, pipeline, timeout ou ownership parallèle
```
## Drain interne des sources désormais borné
Avant cette tranche, le drain global Worker était borné par `shutdown_drain_timeout`, mais le superviseur interne des sources pouvait encore attendre indéfiniment un sibling non coopératif après une faute de source.
`supervise_live_source_tasks` reçoit maintenant la même deadline déjà configurée dans `RawTransactionIngestSettings` :
```text
settings.shutdown_drain_timeout()
```
`drain_live_source_tasks` applique :
```text
tokio::time::timeout(shutdown_drain_timeout, drain)
```
En cas d'expiration :
```text
children.abort_all()
join de tous les children annulés
```
Aucune tâche source ne reste donc détenue après retour du superviseur interne.
## Priorité de faute
Si le shutdown interne commence après une faute source déjà observée et que le sibling ne coopère pas avant la deadline :
```text
la première faute source reste la faute retournée
```
Le timeout ne remplace pas une cause terminale déjà connue.
Si aucun défaut antérieur n'existe et qu'un stop coopératif ne peut pas joindre une source avant la deadline :
```text
ERROR_CODE_RAW_TRANSACTION_INGEST_DRAIN_TIMEOUT
```
est retourné.
Cette règle aligne le superviseur source avec le drain Worker global déjà validé.
## Stop avant mutation de coverage HTTP
Le polling HTTP vérifie maintenant explicitement le stop après le traitement des blocs découverts et avant :
```text
record_coverage_epoch(...)
processing_frontier.publish()
```
Un stop déjà observé ne peut donc pas être suivi d'une nouvelle preuve de coverage de cette fenêtre.
Les appels réseau restent stop-preemptible via les `tokio::select!` existants ; aucun retry Worker n'est ajouté.
## Stop pendant hydration/admission
`RawTransactionIngestHydrationCoordinator::handle_joined` vérifie maintenant le stop :
```text
avant de retirer/appliquer le résultat d'hydration joint
avant chaque signal coalescé de ce résultat
```
Les `tokio::select!` existants restent en place autour de l'admission async.
Un stop déjà observé ne poursuit donc pas la mutation continuity ou l'admission d'un résultat d'hydration devenu prêt au même moment.
## Invariants préservés
```text
Transport reste propriétaire des sockets, reconnects et replay natif
Worker ne modifie jamais from_slot
aucun respawn de source
aucun second scheduler ou sémaphore
fairness pre.010 inchangée
observabilité pre.011 inchangée
Common RAW/admission/persistence restent uniques
Store reste accessible via ksp-store-lib seulement
aucun Worker -> Config/Job/backend Store
aucune nouvelle dépendance
```
## Tests ajoutés
Deux canaris runtime reproduisent les races que cette tranche ferme :
```text
v0_3_14_pre_012_source_fault_preserves_first_fault_and_aborts_non_cooperative_sibling
v0_3_14_pre_012_stop_aborts_non_cooperative_source_after_bounded_drain
```
Le premier prouve :
```text
sibling réellement démarré
faute source observée
sibling volontairement non coopératif
deadline interne atteinte
abort + join du sibling
première faute préservée
aucune tâche active après retour
```
Le second prouve :
```text
stop coopératif demandé
source volontairement non coopérative
deadline interne atteinte
ERROR_CODE_RAW_TRANSACTION_INGEST_DRAIN_TIMEOUT
abort + join
aucune tâche active après retour
```
Les canaris `hardening`, `dependency_boundary` et `release_completeness` vérifient en plus :
```text
deadline source et deadline Worker présentes
abort_all + join présents aux deux niveaux
checkpoints stop avant les mutations repair/admission concernées
aucune nouvelle dépendance ni frontière backend
aucune croissance de surface publique shutdown
```
## Hors périmètre inchangé
```text
aucune nouvelle stratégie de repair
aucune nouvelle méthode HTTP
aucun changement de health policy
aucun changement de snapshot public
aucun changement de TargetCoverage
aucun changement de fairness
aucun changement de persistence semantics
aucun EARLY/shred
aucun Job Backfill depuis Worker
```
## Fichiers ajoutés
```text
deltas/0.3.14/pre.012.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
```
## Fichiers supprimés
```text
aucun
```
## Version Cargo
Conformément au workflow prerelease non-fix :
```text
header Cargo.toml : 586 -> 587
workspace.package.version : 0.3.14-pre.11.fix.2 -> 0.3.14-pre.12
```
## Gate opérateur après application
```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-worker-raw-transaction-ingest-lib --all-targets --all-features
```

View File

@@ -0,0 +1,88 @@
<!-- file: deltas/0.3.14/pre.013-fix.001.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.013-fix.001` — retours explicites dans les closures de canari public API
## Base requise
```text
0.3.14-pre.013
workspace.package.version = 0.3.14-pre.13
deltas/0.3.14/pre.013.md présent
```
## Défaut observé au gate opérateur
Les audits et `cargo check --workspace` passent, les `152` tests unitaires Worker et toutes les suites d'intégration exécutées après compilation passent, mais Clippy `-D warnings` échoue dans le nouveau canari `v0_3_14_pre_013_completeness_closure_adds_no_public_surface` :
```text
clippy::implicit-return
public_api.rs:486: closure filter sans return explicite
public_api.rs:508: closure any sans return explicite
```
## Correction
Les deux closures utilisent désormais un `return` explicite conformément à la règle workspace :
```rust
filter(|line| return line.trim().starts_with("pub use "))
any(|line| return line.trim().starts_with("pub use ") && line.contains(forbidden))
```
La logique du canari reste strictement identique.
## Périmètre
```text
aucun code production modifié
aucune API modifiée
aucune dépendance modifiée
aucune règle de redaction modifiée
aucun changement de pipeline ou d'ownership
aucun relâchement de lint
```
## Fichiers ajoutés
```text
deltas/0.3.14/pre.013-fix.001.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/tests/public_api.rs
```
## Fichiers supprimés
```text
aucun
```
## Version Cargo
```text
header Cargo.toml : 588 -> 589
workspace.package.version : 0.3.14-pre.13 -> 0.3.14-pre.13.fix.1
```
Version du fichier Rust modifié :
```text
tests/public_api.rs : 24 -> 25
```
## Gate opérateur après application
```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-worker-raw-transaction-ingest-lib --all-targets --all-features
```

294
deltas/0.3.14/pre.013.md Normal file
View File

@@ -0,0 +1,294 @@
<!-- file: deltas/0.3.14/pre.013.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.013` — completeness/security cross-layer
## Base requise
```text
0.3.14-pre.012
workspace.package.version = 0.3.14-pre.12
deltas/0.3.14/pre.012.md présent
```
## Gate de la base
Le gate opérateur de `0.3.14-pre.012` est validé avant ouverture de cette tranche :
```text
cargo fmt --all : PASS
cargo fmt --all -- --check : PASS
audit Rust workspace rules : PASS
audit Markdown tables : PASS
cargo check --workspace : PASS
cargo clippy --workspace --all-targets --all-features -- -D warnings : PASS
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features : PASS
```
Le gate Worker comprend notamment :
```text
152 unit tests : PASS
cross_layer_completeness : 8 PASS
dependency_boundary : 20 PASS
hardening : 37 PASS
public_api : 21 PASS
release_completeness : 14 PASS
```
## Objectif
Implémenter strictement la tranche `pre.013` du plan `035` sans nouveau comportement :
```text
fermer les canaris Worker / Transport / Common RAW / Store
prouver que le pipeline d'acquisition/persistence reste unique
prouver la redaction des chemins gap/repair/terminal
prouver la non-régression Legacy / V0 / V1 après le hardening 0.3.14
prouver la séparation bidirectionnelle Worker / Job Backfill
figer le graphe de dépendances Worker
figer l'absence de croissance de surface publique
```
Aucun fichier de production n'est modifié dans cette tranche.
## Pipeline cross-layer unique
Le nouveau canari :
```text
v0_3_14_pre_013_gap_repair_closure_keeps_one_transport_worker_common_raw_store_pipeline
```
vérifie simultanément :
```text
getTransaction observed reste Transport-owned
getBlock observed reste Transport-owned
ContinuityContracts restent privés au Worker
le registre global d'hydration reste unique
l'admission centrale reste unique
la convergence persistence reste unique
Common RAW reste la représentation canonique commune
Store API porte RawTransaction + RawTransactionObservation
Store facade reste l'unique passage vers persistence/observation
```
Il rejette en plus les bypass directs Worker vers :
```text
backend Postgres
Config
Job Backfill
reqwest
tokio-tungstenite bas niveau
yellowstone-grpc-proto
```
## Redaction cross-layer
Le canari :
```text
v0_3_14_pre_013_security_redaction_covers_gap_repair_and_terminal_paths
```
ferme ensemble :
```text
redaction Transport Yellowstone
redaction des erreurs unary distantes
health source-neutral pre.009
fairness privée pre.010
observabilité gap source-neutral pre.011
shutdown terminal borné pre.012
Common RAW Debug redacted
Store API Debug redacted
Store facade secret canary
backend error bridge sans texte externe/secret
```
Le crate root Worker reste exempt de :
```text
source_key
provider_gap
endpoint_gap
signature_gap
payload_gap
```
## Legacy / V0 / V1
Le canari :
```text
v0_3_14_pre_013_legacy_v0_v1_remains_proven_after_gap_repair_hardening
```
réunit les preuves suivantes :
```text
Transport Yellowstone décode les formes courantes dont V1
Standard Block et HTTP Block Polling Worker préservent Legacy/V0/V1
la réconciliation pre.008 ne réécrit pas la représentation RAW
le shutdown pre.012 ne modifie pas la qualification du matériau
Common RAW garde ses goldens Legacy/V0/V1
Store API reste constructible depuis les types RAW communs
```
Aucune nouvelle logique de version de transaction n'est introduite.
## Séparation Worker / Job Backfill
Le canari :
```text
v0_3_14_pre_013_worker_and_backfill_remain_independent_producers
```
prouve depuis les manifests :
```text
Worker -X-> ksp-job-api
Worker -X-> ksp-job-backfill-lib
Job Backfill -X-> ksp-worker-api
Job Backfill -X-> ksp-worker-raw-transaction-ingest-lib
```
Les deux producteurs peuvent continuer à partager uniquement les façades communes nécessaires :
```text
ksp-onchain-transport-lib
ksp-raw-transaction-lib
ksp-store-lib
```
Aucun checkpoint, scheduler ou orchestration commun n'est ajouté.
## Dépendances et API publique
`dependency_boundary` conserve l'inventaire exact Worker :
```text
ksp-core-lib
ksp-logging-lib
ksp-onchain-transport-lib
ksp-raw-transaction-lib
ksp-store-lib
ksp-worker-api
sha2
tokio
```
`public_api` fixe à nouveau :
```text
43 pub use publics
aucun pub mod
aucun ContinuityContracts public
aucun GlobalHydrationRegistry public
aucun SourceInventory public
aucun TrafficClass/FairTurnGate public
aucun SourceLossDecision public
```
La surface publique de `pre.011` reste donc la dernière croissance intentionnelle de `0.3.14` avant le gate technique.
## Canaris ajoutés
```text
cross_layer_completeness
v0_3_14_pre_013_gap_repair_closure_keeps_one_transport_worker_common_raw_store_pipeline
v0_3_14_pre_013_security_redaction_covers_gap_repair_and_terminal_paths
v0_3_14_pre_013_legacy_v0_v1_remains_proven_after_gap_repair_hardening
v0_3_14_pre_013_worker_and_backfill_remain_independent_producers
dependency_boundary
v0_3_14_pre_013_cross_layer_closure_keeps_dependency_and_producer_boundaries_exact
hardening
v0_3_14_pre_013_cross_layer_completeness_security_closure_is_exact_and_redacted
public_api
v0_3_14_pre_013_completeness_closure_adds_no_public_surface
release_completeness
v0_3_14_pre_013_cross_layer_closure_canaries_are_complete_without_behavior_or_surface_growth
```
## Invariants préservés
```text
aucun code production modifié
aucune nouvelle dépendance
aucune nouvelle feature
aucun changement Config
aucun changement Transport runtime
aucun changement Common RAW runtime
aucun changement Store runtime
aucune nouvelle API Worker
aucun Worker -> Job Backfill
aucun Job Backfill -> Worker
aucun second pipeline
aucun nouveau provider
aucun EARLY/shred
```
## Fichiers ajoutés
```text
deltas/0.3.14/pre.013.md
```
## Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/tests/cross_layer_completeness.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/public_api.rs
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
```
## Fichiers supprimés
```text
aucun
```
## Version Cargo
Conformément au workflow prerelease non-fix :
```text
header Cargo.toml : 587 -> 588
workspace.package.version : 0.3.14-pre.12 -> 0.3.14-pre.13
```
## Gate local d'assemblage
Exécuté dans le sandbox après modification :
```text
General Rust rule audit : clean
Rust export completeness audit : 0 candidate(s)
KSP workspace Rust rule audit : clean
Markdown table audit : clean
cargo / rustc / rustfmt : NON DISPONIBLES dans le sandbox
```
Aucune commande Cargo n'est déclarée PASS localement.
## Gate opérateur après application
```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-worker-raw-transaction-ingest-lib --all-targets --all-features
```

114
deltas/0.3.14/pre.014.md Normal file
View File

@@ -0,0 +1,114 @@
<!-- file: deltas/0.3.14/pre.014.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.014` — gate technique/live
## Base requise
```text
0.3.14-pre.013-fix.001
```
## Objectif
Matérialiser la tranche technique/live finale de `0.3.14` sans ajouter de scope fonctionnel : enregistrer le gate d'entrée réellement communiqué, définir le gate déterministe workspace complet à rejouer sur `pre.014`, puis séparer explicitement les smokes live keyless des smokes provider/resource-gated.
## Gate d'entrée réellement reçu
Le gate opérateur du 12 septembre 2026 sur `0.3.14-pre.13.fix.1` est vert pour :
```text
fmt : PASS
audits Rust : clean / export completeness 0
Markdown : clean, 340 tables / 881 files
cargo check --workspace : PASS
clippy workspace/all-targets/all-features -D warnings : PASS
Worker : 152 unit + 12 cross-layer + 21 dependency-boundary + 38 hardening + 22 public-api + 15 release-completeness, 0 échec
```
Ce journal ne contient ni test workspace exhaustif, ni graphes Cargo, ni smoke live. Aucun de ces éléments n'est donc déclaré PASS avant exécution réelle sur `pre.014`.
## Modifications
```text
Cargo.toml
header 589 -> 590
workspace.package.version -> 0.3.14-pre.14
docs/validation/031-V0_3_14_MULTI_SOURCE_GAP_REPAIR_HARDENING.md
journal du gate d'entrée
gate workspace final à exécuter
graphes/duplicates à produire
smokes keyless à exécuter
smokes provider/resource-gated explicitement NON EXÉCUTÉ sans provisionnement
deltas/0.3.14/pre.014.md
ajouté
```
Aucun code Rust, test, dépendance, feature ou contrat public n'est modifié.
## Gate déterministe final requis
```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 --workspace --all-targets --all-features
cargo tree -p ksp-worker-raw-transaction-ingest-lib --edges normal
cargo tree -p ksp-worker-raw-transaction-ingest-lib -e features
cargo tree --duplicates
```
`cargo tree --duplicates` est un inventaire : une sortie non vide n'est pas à elle seule un défaut si les frontières de dépendances restent vertes.
## Smokes keyless finaux requis
```bash
cargo test -p ksp-onchain-transport-lib --test transport_devnet_smoke -- --ignored --nocapture
cargo test -p ksp-onchain-transport-lib --test websocket_devnet_smoke -- --ignored --nocapture
```
Ces tests doivent être exécutés explicitement ; leur simple compilation dans le workspace ne constitue pas une preuve live.
## Smokes conditionnels / non-claims
Sans credentials ou ressources opérateur, conserver explicitement :
```text
OrbitFlare Yellowstone Devnet : NON EXÉCUTÉ
PublicNode Yellowstone : NON EXÉCUTÉ
Helius transactionSubscribe : NON EXÉCUTÉ
Postgres live Store : NON EXÉCUTÉ
Worker repair -> Store live end-to-end : NON EXÉCUTÉ sans environnement dédié
```
Aucun résultat historique `0.3.13` n'est requalifié comme PASS `0.3.14`.
## Validations locales d'assemblage
Exécutées après modification :
```text
General Rust rule audit : clean
Rust export completeness audit : 0 candidate(s)
KSP workspace Rust rule audit : clean
Markdown table audit : clean (340 tables / 882 files)
```
Cargo/Rustfmt ne sont pas disponibles dans l'environnement d'assemblage ; aucun PASS Cargo post-bump n'est revendiqué localement.
## Frontières
```text
aucun nouveau comportement
aucune API publique modifiée
aucune dépendance/feature modifiée
aucun README/USAGE/architecture réconcilié ici
aucun CHANGELOG/ROADMAP/prompt suivant modifié
```
La réconciliation documentaire reste réservée à `pre.015` et ne peut commencer qu'après fermeture réelle du gate technique/live `pre.014`.

194
deltas/0.3.14/pre.015.md Normal file
View File

@@ -0,0 +1,194 @@
<!-- file: deltas/0.3.14/pre.015.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.015` — réconciliation documentaire
## 1. Base requise
```text
0.3.14-pre.014
workspace.package.version = 0.3.14-pre.14
```
Le gate opérateur communiqué le **12 septembre 2026** sur `pre.014` est vert pour toutes les commandes déterministes exécutées. Le workspace complet totalise `1931` tests passés, `0` échec et `15` ignored ; les deux smokes keyless HTTP/WebSocket Devnet passent chacun `1/1` lorsqu'ils sont exécutés explicitement avec `--ignored`.
## 2. Objectif
`pre.015` réconcilie la documentation durable avec l'état réellement livré par `0.3.14`.
Cette tranche ne change aucun comportement runtime. Elle corrige notamment les descriptions héritées de `0.3.13` qui affirmaient encore qu'une perte de source ou un gap de rétention provoquait systématiquement un fault immédiat, alors que `0.3.14` autorise désormais une continuation uniquement sous preuves de réconciliation passée et de `TargetCoverage` futur.
Conformément au forecast, `CHANGELOG.md`, `ROADMAP.md` et le prompt de la version suivante restent hors de cette tranche et sont réservés à `pre.016`.
## 3. Version workspace
Cette prerelease non-fix synchronise Cargo conformément à `VER-ID-009` :
```text
0.3.14-pre.14
->
0.3.14-pre.15
```
Le header racine de `Cargo.toml` passe de `590` à `591`.
## 4. Documentation Worker réconciliée
`crates/ksp-worker-raw-transaction-ingest-lib/README.md` et `USAGE.md` documentent désormais l'état `0.3.14` :
```text
processing frontier != continuity frontier
gaps run-local inclusifs et bornés
TargetCoverage conservative
replay Transport != coverage d'intervalle
coverage redondante uniquement par preuve structurale
scan HTTP / block fetch / transaction hydration bornés
source-loss continuation uniquement après réconciliation complète
health gap-aware et future-coverage-aware
fairness nominal/repair sur les mêmes ressources
snapshots publics gap/repair source-neutral
aucun Worker -> Job Backfill
aucun checkpoint ou historique caller-paramétrable
```
Les bornes documentées restent :
```text
open gaps <= 64
range <= 4096 slots
discovery HTTP <= 512 slots par fenêtre
getBlock logiques concurrents <= 4
repair actif simultané <= 1
```
## 5. Architectures réconciliées
Les documents réellement affectés sont alignés :
```text
docs/architecture/004-COMPONENT_INVENTORY.md
Worker RAW étendu jusqu'à 0.3.14
docs/architecture/005-DEPENDENCY_GRAPH.md
dépendances inchangées ; continuité/coverage/repair final documenté
docs/architecture/009-ACQUISITION_WORKERS_AND_JOBS.md
cinq familles live + séparation Worker/Backfill conservée
source-loss non terminale uniquement sous preuves explicites
docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md
état Worker après 0.3.14
réconciliation run-local et reports actualisés
```
Aucune nouvelle dépendance d'architecture n'est introduite.
## 6. Plans et validation réconciliés
`docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md` remplace le placeholder `0.3.14` par son rôle réellement livré : gap repair / continuité run-local multi-source + observabilité/hardening.
`docs/plans/035-V0_3_14_MULTI_SOURCE_GAP_REPAIR_HARDENING_PLAN.md` enregistre le forecast effectivement exécuté de `pre.001` à `pre.015`, les bornes finales et l'absence d'expansion EARLY/provider/historique.
`docs/validation/031-V0_3_14_MULTI_SOURCE_GAP_REPAIR_HARDENING.md` enregistre le gate final `pre.014` et les smokes keyless réellement exécutés.
## 7. Gate opérateur `pre.014` enregistré
```text
cargo fmt --all : PASS
cargo fmt --all -- --check : PASS
General Rust rule audit : clean
Rust export completeness audit : 0 candidate(s)
KSP workspace Rust rule audit : clean
Markdown table audit : clean (340 tables / 882 files)
cargo check --workspace : PASS
cargo clippy --workspace --all-targets --all-features -- -D warnings : PASS
cargo test --workspace --all-targets --all-features : PASS
workspace tests : 1931 passed / 0 failed / 15 ignored
cargo tree -p ksp-worker-raw-transaction-ingest-lib --edges normal : exécuté
cargo tree -p ksp-worker-raw-transaction-ingest-lib -e features : exécuté
cargo tree --duplicates : exécuté
HTTP Devnet smoke explicite : 1 passed / 0 failed
WebSocket Devnet smoke explicite : 1 passed / 0 failed
```
Les smokes OrbitFlare/PublicNode Yellowstone, Helius transactionSubscribe, PostgreSQL live et Worker repair -> Store live restent explicitement `NON EXÉCUTÉ` sans environnement/credential provisionné.
## 8. Fichiers ajoutés
```text
deltas/0.3.14/pre.015.md
```
## 9. Fichiers modifiés
```text
Cargo.toml
crates/ksp-worker-raw-transaction-ingest-lib/README.md
crates/ksp-worker-raw-transaction-ingest-lib/USAGE.md
docs/architecture/004-COMPONENT_INVENTORY.md
docs/architecture/005-DEPENDENCY_GRAPH.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/035-V0_3_14_MULTI_SOURCE_GAP_REPAIR_HARDENING_PLAN.md
docs/validation/031-V0_3_14_MULTI_SOURCE_GAP_REPAIR_HARDENING.md
```
## 10. Fichiers supprimés
```text
aucun
```
## 11. Surfaces explicitement inchangées
```text
CHANGELOG.md
ROADMAP.md
README.md racine
RULES.md
prompts/**
config/**
crates/**/src/**
crates/**/tests/**
crates/**/unit_tests/**
Cargo.toml de crates
features/dépendances
schemas/migrations
```
Aucun delta publié antérieur n'est modifié.
## 12. Validations exécutées localement
Sur l'état exact `pre.015` avant packaging :
```text
General Rust rule audit : clean
Rust export completeness audit : 0 candidate(s)
KSP workspace Rust rule audit : clean
Markdown table audit : clean (340 tables / 883 files)
diff pre.014 -> pre.015 : 1 ajout / 10 modifications / 0 suppression
Cargo.toml : hors header, seule workspace.package.version change
CHANGELOG.md : byte-identique à pre.014
ROADMAP.md : byte-identique à pre.014
prompts/** : byte-identiques à pre.014
aucun fichier Rust/test/config/schema/migration/manifest de crate modifié
```
Le toolchain Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage ; aucun nouveau PASS Cargo post-bump n'est revendiqué localement.
Le gate opérateur `pre.014` reste la preuve technique précédente ; un gate minimal `pre.015` est demandé après application pour confirmer le bump/documentation exacts.
## 13. Décisions prises
- `0.3.14` est techniquement fermé par le gate `pre.014` et documentairement réconcilié par `pre.015` ;
- les cinq familles live restent inchangées ; `0.3.14` durcit leur continuité dans le run actif ;
- reconnect, replay natif et retries réseau restent Transport-owned ;
- le Worker ne lance jamais de Job Backfill et ne reçoit aucune campagne historique caller-paramétrable ;
- une source perdue ne peut rester absente que sous réconciliation passée complète et coverage future prouvée ;
- les détails de `0.3.15`, le prochain prompt, CHANGELOG et ROADMAP sont différés à `pre.016`.
## 14. Questions ouvertes
Aucune question ouverte bloquante pour `pre.015`.

203
deltas/0.3.14/pre.016.md Normal file
View File

@@ -0,0 +1,203 @@
<!-- file: deltas/0.3.14/pre.016.md -->
<!-- version: 1 -->
# Delta `0.3.14-pre.016` — préparation de publication
## 1. Base requise
```text
0.3.14-pre.015
workspace.package.version = 0.3.14-pre.15
```
Le gate opérateur communiqué le **12 septembre 2026 à 23:07** sur `pre.015` est vert pour toutes les commandes exécutées :
```text
cargo fmt --all -- --check : PASS
General Rust rule audit : clean
Rust export completeness audit : 0 candidate(s)
KSP workspace Rust rule audit : clean
Markdown table audit : clean, 340 tables / 883 files
cargo check --workspace : PASS
```
Conformément à `pre.015`, aucun rerun Clippy/tests/smokes n'était requis pour cette tranche documentaire hors bump mécanique.
## 2. Objectif
`pre.016` est la dernière prerelease de préparation de publication de `0.3.14`.
Conformément à `VER-LIFECYCLE-003` et `PROMPT_STRUCTURE.md`, elle modifie fonctionnellement uniquement :
```text
prompt de démarrage 0.3.15
CHANGELOG.md
ROADMAP.md
```
Les seuls fichiers mécaniques supplémentaires sont :
```text
Cargo.toml racine
delta pre.016
```
Aucun README, USAGE, plan, validation, architecture, code, test, Config, schema, dépendance, feature ou manifest de crate n'est rouvert.
## 3. Version workspace
La prerelease non-fix synchronise Cargo :
```text
0.3.14-pre.15
->
0.3.14-pre.16
```
Le header de `Cargo.toml` passe de `591` à `592`.
## 4. Prompt suivant
Le nouveau prompt :
```text
prompts/034-V0_3_15_START_PROMPT.md
```
ouvre `0.3.15` exclusivement depuis le futur stable :
```text
v0.3.14
```
Sa mission est `ksp-app-raw-transaction-ingest-desk`, avec les décisions opératoires suivantes figées :
```text
route != source != endpoint
route = stratégie d'acquisition complète + capabilities obligatoires
une route peut nécessiter plusieurs ressources, par exemple gRPC + HTTP ou WS + HTTP
route sélectionnable seulement si toutes ses requirements sont composables depuis Config sur le même réseau
provider name seul ne vaut jamais capability
composabilité Config déterministe != opérabilité runtime
Config revalidée au Start
1 route sélectionnée = 1 Worker réel
pas de Worker créé par source physique
plusieurs Workers/Routes peuvent partager le même Store
pas de partage implicite de gap/TargetCoverage entre Workers distincts
frontend Tauri = surface sûre ; endpoints/secrets/handles restent Rust-only
Desk compose Config + Transport + Store + Worker sans réimplémenter l'ingestion
```
Le prompt impose un `pre.001` d'audit/sizing avant scaffold lourd et réserve :
```text
docs/plans/036-V0_3_15_RAW_TRANSACTION_INGEST_DESK_PLAN.md
docs/validation/032-V0_3_15_RAW_TRANSACTION_INGEST_DESK.md
```
## 5. CHANGELOG
`CHANGELOG.md` reçoit l'entrée stable `0.3.14` synthétisant :
```text
gaps run-local et bornes finales
reconnect/replay/delivery/coverage distincts
TargetCoverage et coverage redondante conservative
scan HTTP + known-reference hydration
réconciliation source-loss et health gap-aware
fairness nominal/repair
snapshots publics gap/repair source-neutral
shutdown/drain hardening
pipeline Common RAW/Store unique
Worker/Backfill indépendants
gate workspace 1 931 PASS / 0 échec / 15 ignored
smokes keyless HTTP Devnet + WebSocket Devnet PASS
preuves provider/resource-gated restantes NON EXÉCUTÉES
handoff 0.3.15 via prompt 034
```
## 6. ROADMAP
L'entrée `0.3.14` passe de `[ ]` à `[X]` et décrit l'état réellement livré.
L'entrée `0.3.15` est précisée pour refléter la décision acquise avant ouverture de la release : **un Worker par route sélectionnée**, route complète composée de toutes les capabilities requises, sélection uniquement lorsque Config permet la composition, puis validation réelle au `Start`.
## 7. Fichiers ajoutés
```text
prompts/034-V0_3_15_START_PROMPT.md
deltas/0.3.14/pre.016.md
```
## 8. Fichiers modifiés
```text
Cargo.toml
CHANGELOG.md
ROADMAP.md
```
## 9. Fichiers supprimés
```text
aucun
```
## 10. Surfaces explicitement inchangées
```text
README.md
RULES.md
docs/**
crates/**
config/**
tests/**
prompts/001-* à prompts/033-*
deltas/0.3.14/pre.001.md à pre.015.md et leurs fixes
```
Aucun delta publié antérieur n'est modifié.
## 11. Validations exécutées localement
Sur l'état exact `pre.016` avant packaging :
```text
General Rust rule audit : clean
Rust export completeness audit : 0 candidate(s)
KSP workspace Rust rule audit : clean
Markdown table audit : clean, 340 tables / 885 files
prompt 034 : 18 sections présentes, dont Première mission / Prévision souple / Validation / Instruction d'ouverture
diff exact pre.015 -> pre.016 : 3 fichiers existants modifiés + 2 ajoutés + 0 supprimé
Cargo.toml : hors header, seule workspace.package.version change
aucun README/USAGE/plan/validation/architecture/code/test/config/schema/dependency/feature modifié
```
Le toolchain Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Aucun nouveau PASS Cargo post-bump n'est revendiqué localement ; les PASS Cargo d'entrée proviennent exclusivement du gate opérateur `pre.015`.
## 12. Gate opérateur avant `rel.001`
Après application :
```bash
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
```
Aucun rerun live n'est requis par cette tranche de publication minimale. Un défaut découvert dans un couloir antérieur ne doit pas être absorbé dans `rel.001`.
## 13. Décisions prises
- `0.3.14` est techniquement et documentairement fermé ; `pre.016` ne rouvre aucun comportement ;
- `0.3.15` commence uniquement après `0.3.14-rel.001` validée et le tag stable `v0.3.14` ;
- la Desk `0.3.15` gère des **routes complètes** et lance un Worker par route sélectionnée ;
- la composabilité d'une route est déterminée depuis Config, l'opérabilité réelle est validée au Start ;
- les preuves live non exécutées de `0.3.14` restent explicitement non revendiquées.
## 14. Questions ouvertes
```text
aucune question bloquante pour la préparation de publication 0.3.14
```

195
deltas/0.3.14/rel.001.md Normal file
View File

@@ -0,0 +1,195 @@
<!-- file: deltas/0.3.14/rel.001.md -->
<!-- version: 1 -->
# Delta `0.3.14-rel.001` — publication stable
## Base requise
Base directe attendue :
```text
0.3.14-pre.016
workspace.package.version = 0.3.14-pre.16
```
Le gate opérateur ciblé de `pre.016`, exécuté le **12 septembre 2026 à 23:25**, est propre :
```text
cargo fmt --all -- --check : PASS
General Rust rule audit : clean
Rust export completeness audit : 0 candidate(s)
KSP workspace Rust rule audit : clean
Markdown table audit : clean (340 table(s), 885 file(s))
cargo check --workspace : PASS
```
Les couloirs de fermeture antérieurs sont déjà fermés :
- `pre.014` : gate technique/live complet, workspace `--all-targets --all-features`, Clippy strict, graphes Cargo et smokes live accessibles ;
- `pre.015` : réconciliation documentaire finale des surfaces `0.3.14` ;
- `pre.016` : préparation minimale de publication avec `CHANGELOG.md`, `ROADMAP.md` et `prompts/034-V0_3_15_START_PROMPT.md`.
## Objet
Publier mécaniquement la version stable :
```text
0.3.14
```
Conformément à `VER-LIFECYCLE-012`, cette tranche ne corrige ni code, ni test, ni documentation durable, ni architecture, ni configuration, ni dépendance, ni prompt.
Tout défaut nouveau renvoie vers une prerelease appropriée ; `rel.001` n'est pas une tranche de rattrapage.
## Modification
### `Cargo.toml`
Le header est incrémenté parce que le fichier est réellement modifié :
```text
592 -> 593
```
La version workspace devient :
```text
0.3.14-pre.16 -> 0.3.14
```
Aucune autre ligne du `Cargo.toml` racine n'est modifiée.
## Fichiers ajoutés
```text
deltas/0.3.14/rel.001.md
```
## Fichiers modifiés
```text
Cargo.toml
```
## Fichiers supprimés
```text
aucun
```
## Surfaces explicitement inchangées
```text
CHANGELOG.md
ROADMAP.md
README.md
RULES.md
prompts/034-V0_3_15_START_PROMPT.md
crates/**
docs/**
config/**
```
La surface préparée en `pre.016` reste byte-identique hors mécanique Cargo et ajout du présent delta.
## Surface stable publiée
`0.3.14` stabilise le hardening gap/repair multi-source de `ksp-worker-raw-transaction-ingest-lib` :
- gaps run-local bornés avec identité, plage, état, raison et méthode de repair observable sans identité provider ;
- distinction explicite entre reconnect, replay attempt, replay delivery, coverage prouvée, repair et unresolved gap ;
- `TargetCoverage` conservateur, sans équivalence cross-family implicite et sans promotion `confirmed` -> `finalized` ;
- coverage redondante structurée avec scopes exacts/superset sûrs et réconciliation uniquement sur preuve complète ;
- bounded HTTP repair discovery, avec fenêtre explicite et absence de preuve inventée pour les slots skipped ;
- known-reference hydration via le registre/semaphore global existant, sans second pipeline ;
- source-loss non terminal uniquement lorsque la continuité et la couverture future sont réellement prouvées ;
- health gap-aware et source-neutral, avec retour à `Healthy` seulement après réconciliation complète ;
- fairness nominal/repair bornée sur les ressources communes, y compris lorsque la capacité globale vaut `1` ;
- observabilité publique des gaps/repair totals et oldest open gap sans URL, secret, signature, payload, provider error ou source key ;
- shutdown/drain source et Worker renforcé avec timeout borné, `abort_all` puis join complet, conservation de la première faute et suppression des publications tardives ;
- canaris cross-layer Transport -> Worker -> Common RAW -> Store fermés, Legacy/V0/V1 non régressés et séparation Worker/Backfill maintenue ;
- aucune dépendance Worker vers Config, Job Backfill ou backend Store physique, aucun client réseau/SDK provider parallèle et aucun second runtime Transport.
Le gate technique `pre.014` a exécuté `cargo test --workspace --all-targets --all-features` avec :
```text
1 931 PASS
0 échec
15 ignored opt-in/operator-only
```
Les smokes live keyless Solana HTTP Devnet et WebSocket Devnet passent `1/1` chacun. Les preuves Yellowstone provider-gated, Helius live et PostgreSQL live restent explicitement non revendiquées lorsqu'elles n'ont pas été provisionnées ; aucune compilation d'un test `ignored` ne les requalifie en PASS.
Le Job Backfill historique paramétré et le Worker Ingest continu restent des producteurs indépendants du même Store. Un Worker ne déclenche pas de campagne Backfill et ne partage pas implicitement ses gaps/`TargetCoverage` avec un autre Worker.
## Suite préparée
La session suivante part exclusivement de la base stable :
```text
v0.3.14
```
et exécute :
```text
prompts/034-V0_3_15_START_PROMPT.md
```
pour ouvrir :
```text
0.3.15 — ksp-app-raw-transaction-ingest-desk
```
Le cadrage préparé impose notamment :
```text
route != source != endpoint
1 route sélectionnée = 1 instance Worker
une route n'est sélectionnable que si toutes ses capabilities obligatoires sont composables depuis Config
la composabilité Config ne vaut pas opérabilité runtime
le Start revalide la configuration et les ressources réelles
plusieurs routes peuvent alimenter le même Store sans partager implicitement leur continuité
```
`0.3.15-pre.001` commence par audit, brainstorming, matrice route/capability et sizing avant toute implémentation lourde du Desk.
## Validations d'assemblage
Sur l'état candidat `rel.001`, avant packaging :
```text
General Rust rule audit : clean
Rust export completeness audit : 0 candidate(s)
KSP workspace Rust rule audit : clean
Markdown table audit : clean (340 table(s), 886 file(s))
```
Les contrôles d'archive et la comparaison byte-exact sont exécutés après packaging avant livraison.
Aucun PASS Cargo post-bump stable n'est revendiqué localement faute de toolchain dans l'environnement d'assemblage.
## Gate demandé après application
La tranche est purement mécanique. Après application sur un `pre.016` dont le gate préalable est vert :
```bash
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/0.3.14
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p ksp-worker-raw-transaction-ingest-lib --all-targets --all-features
cargo tree -p ksp-worker-raw-transaction-ingest-lib --edges normal
cargo tree -p ksp-worker-raw-transaction-ingest-lib -e features
```
Après gate propre :
```text
commit : v0.3.14-rel.001
tag : v0.3.14
```
Aucun tag de prerelease ou `rel.001` n'est requis.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/004-COMPONENT_INVENTORY.md --> <!-- file: docs/architecture/004-COMPONENT_INVENTORY.md -->
<!-- version: 40 --> <!-- version: 41 -->
# Inventaire initial des composants KSP # Inventaire initial des composants KSP
@@ -46,7 +46,7 @@ Ce document maintient l'inventaire synthétique des composants retenus ou presse
| 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` | inspection RAW read-only Transaction/Account/Observation via façade Store |
| 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.13` | fondation + 5 familles live, convergence multi-source, fairness, health et shutdown bornés | | 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 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 |

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/005-DEPENDENCY_GRAPH.md --> <!-- file: docs/architecture/005-DEPENDENCY_GRAPH.md -->
<!-- version: 29 --> <!-- version: 30 -->
# Graphe de dépendances KSP # Graphe de dépendances KSP
@@ -433,9 +433,9 @@ ksp-worker-control-lib # composant retenu, non matérialis
-> ksp-core-lib -> ksp-core-lib
``` ```
`ksp-worker-api` est ouvert en `0.3.9` comme contrat générique de services continus et ne connaît ni Solana, ni Transport, ni Store, ni Tauri. `ksp-worker-raw-transaction-ingest-lib` est son premier consumer concret : sa fondation `0.3.11` possède admission/persistence/snapshots/shutdown ; `0.3.12` ouvre la première verticale Yellowstone + hydration HTTP ; `0.3.13` matérialise la composition live multi-source. Config et le backend physique restent hors du Worker. `ksp-worker-api` est ouvert en `0.3.9` comme contrat générique de services continus et ne connaît ni Solana, ni Transport, ni Store, ni Tauri. `ksp-worker-raw-transaction-ingest-lib` est son premier consumer concret : sa fondation `0.3.11` possède admission/persistence/snapshots/shutdown ; `0.3.12` ouvre la première verticale Yellowstone + hydration HTTP ; `0.3.13` matérialise la composition live multi-source ; `0.3.14` ferme la continuité run-local par gaps bornés, preuves de coverage, repair conservatif, health gap-aware et shutdown/fairness durcis. Config et le backend physique restent hors du Worker.
La verticale actuelle n'est ni « un worker WebSocket » ni un moteur historique : `RawTransactionIngestRuntimeResources` contient de `1` à `32` sources logiques validées d'un même réseau parmi cinq familles — Yellowstone, Standard WS `logsSubscribe`, Standard WS `blockSubscribe`, Helius `transactionSubscribe` et HTTP live block polling. Yellowstone, Standard Logs et Helius convergent vers une registry globale d'hydration HTTP `getTransaction`; Standard Block et HTTP Block Polling qualifient directement le wire Base64 Legacy/V0/V1 vers Common RAW. Toutes les acquisitions rejoignent la même admission/persistence, la convergence canonique `(network, signature)` et des observations distinctes par provenance. Reconnect, `from_slot` et `SubscribeReplayInfo` restent propriétaires de Transport ; le Worker observe leur projection sûre, conserve une processing frontier run-local, applique des bornes/fairness globales et fault sur un gap de rétention prouvé sans appeler le Job Backfill. La verticale actuelle n'est ni « un worker WebSocket » ni un moteur historique : `RawTransactionIngestRuntimeResources` contient de `1` à `32` sources logiques validées d'un même réseau parmi cinq familles — Yellowstone, Standard WS `logsSubscribe`, Standard WS `blockSubscribe`, Helius `transactionSubscribe` et HTTP live block polling. Yellowstone, Standard Logs et Helius convergent vers une registry globale d'hydration HTTP `getTransaction`; Standard Block et HTTP Block Polling qualifient directement le wire Base64 Legacy/V0/V1 vers Common RAW. Toutes les acquisitions rejoignent la même admission/persistence, la convergence canonique `(network, signature)` et des observations distinctes par provenance. Reconnect, `from_slot`, replay natif et retries réseau restent propriétaires de Transport. Le Worker observe leurs preuves sûres, maintient processing/continuity frontiers distinctes, réconcilie uniquement des gaps du run courant avec coverage explicite et mécanismes bornés, et n'appelle jamais le Job Backfill. Une source perdue ne peut rester absente que si sa perte passée est réconciliée et si les sources restantes prouvent encore tout le `TargetCoverage` futur.
RAW worker puis STRUCTURAL worker sont introduits à la fin de leur couche respective, lorsque persistence/backlog sont disponibles. Le traitement RAW -> STRUCTURAL borné est porté par un STRUCTURAL job distinct du service continu. Les workers DECODED/DOMAIN sont introduits avec les groupes Program concernés plutôt que tous anticipés en bloc. RAW worker puis STRUCTURAL worker sont introduits à la fin de leur couche respective, lorsque persistence/backlog sont disponibles. Le traitement RAW -> STRUCTURAL borné est porté par un STRUCTURAL job distinct du service continu. Les workers DECODED/DOMAIN sont introduits avec les groupes Program concernés plutôt que tous anticipés en bloc.

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: 19 --> <!-- version: 20 -->
# Acquisition, workers, jobs et pipelines spécialisés # Acquisition, workers, jobs et pipelines spécialisés
@@ -131,9 +131,9 @@ normalisation RawTransaction commune
ksp-store-lib ksp-store-lib
``` ```
Le modèle général autorise des stratégies alternatives, complémentaires ou redondantes, mais la verticale productive actuelle est volontairement plus étroite : **une** source Yellowstone + **une** voie HTTP d'hydration. Ce choix n'inscrit pas le provider ou le protocole dans l'identité RAW et ne ferme pas l'architecture future multi-source. La verticale productive accepte désormais de `1` à `32` sources logiques d'un même réseau parmi cinq familles : Yellowstone, Standard WS `logsSubscribe`, Standard WS `blockSubscribe`, Helius `transactionSubscribe` et HTTP live block polling. Ces sources peuvent être complémentaires ou redondantes, mais aucune équivalence n'est supposée par le seul provider/protocole : la convergence canonique reste `(network, signature)` et la coverage utilise des scopes structuraux explicites.
Le reconnect/replay du stream est une capacité Transport. Le Worker conserve une processing frontier run-local sur le travail réellement observé, projette `Active/Reconnecting/Closing/Closed/Failed` et des compteurs reconnect/replay/gap, mais ne possède ni checkpoint durable ni campagne de gap repair. Un gap de rétention prouvé par Transport devient un fault `source_failed` ; une récupération historique éventuelle reste une responsabilité séparée du Job Backfill. Le reconnect/replay du stream est une capacité Transport. Le Worker conserve une processing frontier run-local sur le travail réellement observé et une continuity frontier séparée, projette des gaps bornés et leurs méthodes de réparation, mais ne possède aucun checkpoint durable ni campagne historique. Un gap du run courant peut être réconcilié par replay Transport, coverage redondante prouvée, scan HTTP borné, block fetch ou hydration d'une référence connue. Une perte de source ne devient non terminale que si sa plage passée est entièrement réconciliée et si les sources restantes couvrent encore tout le `TargetCoverage` futur. Toute lacune non bornable/non couverte reste terminale ; aucune récupération n'appelle le Job Backfill.
#### Résultat de l'audit `0.3.9` #### Résultat de l'audit `0.3.9`

View File

@@ -1,5 +1,5 @@
<!-- file: docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md --> <!-- file: docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md -->
<!-- version: 14 --> <!-- version: 15 -->
# Acquisition et alimentation `RawTransaction` # Acquisition et alimentation `RawTransaction`
@@ -187,7 +187,7 @@ Yellowstone BlockMeta / Slot
-> continuité run-local uniquement -> continuité run-local uniquement
``` ```
La composition est caller-owned : le Worker ne lit pas Config et ne reçoit pas de secret. `RawTransactionIngestRuntimeResources` accepte de `1` à `32` sources logiques d'un même réseau, refuse les identités source dupliquées et supervise toutes les sources simultanément. Une faute source reste terminale pour le Worker faute d'équivalence de coverage prouvée. La composition est caller-owned : le Worker ne lit pas Config et ne reçoit pas de secret. `RawTransactionIngestRuntimeResources` accepte de `1` à `32` sources logiques d'un même réseau, refuse les identités source dupliquées et supervise toutes les sources simultanément. Une faute source n'autorise la continuation des siblings que lorsque sa plage de perte est sûre, que la coverage passée est réconciliée et que les sources restantes couvrent encore tout le `TargetCoverage` futur. Aucune équivalence n'est déduite du provider ou du protocole.
Il ne lance pas de campagne historique arbitraire. Il ne lance pas de campagne historique arbitraire.
@@ -204,17 +204,20 @@ Store durable = persistence des acquisitions admises
blockchain completeness = non prouvée par les métriques ci-dessus blockchain completeness = non prouvée par les métriques ci-dessus
``` ```
Une tentative de replay ne prouve ni succès ni continuité parfaite. Si Transport prouve qu'un slot demandé est antérieur à `first_available`, son compteur de continuity gap augmente ; le Worker projette ce gap puis termine avec un fault `source_failed`. Une tentative de replay ne prouve ni succès ni continuité parfaite. Si Transport prouve qu'un slot demandé est antérieur à `first_available`, son compteur de continuity gap augmente ; le Worker ouvre ou enrichit alors un gap run-local sans considérer la reconnexion comme une réparation.
Il n'existe pas de délégation automatique vers Backfill : La réconciliation reste bornée au run :
```text ```text
gap de rétention prouvé gap de continuité borné
-> Worker fault/stop -> replay Transport si preuve exploitable
-> aucun lancement de campagne historique -> coverage redondante structurale si prouvée
-> scan HTTP borné / getBlock / getTransaction selon capability
-> même Common RAW / admission / Store
-> Repaired ou Unresolved explicite
``` ```
Inversement, une demande telle que « récupère les transactions du programme X depuis le slot Y » reste une campagne `ksp-job-backfill-lib`. Il n'existe toujours aucune délégation automatique vers Backfill. Une demande telle que « récupère les transactions du programme X depuis le slot Y » reste une campagne `ksp-job-backfill-lib`, tandis qu'un gap non bornable ou non réconciliable du run courant fait fault le Worker.
### 3.4 Absence de relation Job ↔ Worker ### 3.4 Absence de relation Job ↔ Worker
@@ -734,7 +737,7 @@ RAW v1 complet -> hydration HTTP avant persistence
Cette qualification cross-source a préparé le contrat sans créer d'edge Common RAW -> Transport. L'extension Worker live utilise désormais cette décision conservative : Yellowstone produit des signaux structurés dans le Worker concret, puis HTTP `getTransaction` fournit le matériau RAW complet avant canonicalisation. Common RAW reste entièrement Transport-neutral. Cette qualification cross-source a préparé le contrat sans créer d'edge Common RAW -> Transport. L'extension Worker live utilise désormais cette décision conservative : Yellowstone produit des signaux structurés dans le Worker concret, puis HTTP `getTransaction` fournit le matériau RAW complet avant canonicalisation. Common RAW reste entièrement Transport-neutral.
## 14. État du Worker live après `0.3.13` ## 14. État du Worker live après `0.3.14`
### 14.0 Verticale matérialisée ### 14.0 Verticale matérialisée
@@ -756,11 +759,14 @@ convergence canonique par (network, signature) + disagreement explicite
persistence atomique via ksp-store-lib en mode Normal + observations supplémentaires idempotentes persistence atomique via ksp-store-lib en mode Normal + observations supplémentaires idempotentes
quotas pending/in-flight par source et bornes globales exactes quotas pending/in-flight par source et bornes globales exactes
fairness minimale / anti-starvation sous duplicate storm fairness minimale / anti-starvation sous duplicate storm
processing frontier run-local multi-source conservative processing frontier et continuity frontier run-local distinctes
ledger de gaps inclusifs bornés + TargetCoverage conservative
replay Transport / coverage redondante / scan HTTP / block fetch / hydration comme preuves distinctes
observabilité publique source-neutral des gaps, raisons, états, dernière méthode et compteurs checked
projection source-neutral Active/Reconnecting/Closing/Closed/Failed + gauges source_total/active/reconnecting/failed projection source-neutral Active/Reconnecting/Closing/Closed/Failed + gauges source_total/active/reconnecting/failed
observation des compteurs reconnect/replay/gap Transport health Healthy/Degraded/Unhealthy fondée sur gaps, frontiers et coverage présente/future
health conservative Healthy/Degraded/Unhealthy fairness nominal/repair sur les mêmes admission/hydration/persistence
fault sur gap de rétention prouvé source loss non terminale uniquement après réconciliation complète et coverage future prouvée
shutdown deadline + abort/join sans tâche orpheline ni late persistence shutdown deadline + abort/join sans tâche orpheline ni late persistence
``` ```
@@ -790,7 +796,8 @@ acquérir à partir du démarrage
normaliser et persister RawTransaction + observations normaliser et persister RawTransaction + observations
publier snapshots/notifications concrètes Worker publier snapshots/notifications concrètes Worker
laisser reconnect/from_slot/replay au Transport laisser reconnect/from_slot/replay au Transport
fault proprement lorsqu'un gap de rétention est prouvé réconcilier uniquement les gaps bornés du run courant avec des preuves de coverage explicites
fault proprement lorsqu'une lacune reste non bornable, non couverte ou unresolved
``` ```
Il ne doit pas : Il ne doit pas :
@@ -805,7 +812,7 @@ hardcoder un provider unique
### 14.2 Sources Worker V1 matérialisées et reports ### 14.2 Sources Worker V1 matérialisées et reports
Matérialisé dans `0.3.13` : Matérialisé dans `0.3.13` puis durci dans `0.3.14` :
```text ```text
Yellowstone transactions / blocks / status + HTTP hydration Yellowstone transactions / blocks / status + HTTP hydration
@@ -815,14 +822,18 @@ Helius transactionSubscribe Full + HTTP hydration
HTTP live block polling getSlot/getBlocksWithLimit/getBlock observed HTTP live block polling getSlot/getBlocksWithLimit/getBlock observed
HTTP hydration partagée et coalescée cross-source HTTP hydration partagée et coalescée cross-source
replay Yellowstone conservé propriétaire de Transport pour continuité du run replay Yellowstone conservé propriétaire de Transport pour continuité du run
gaps run-local bornés et continuity frontier distincte de la processing frontier
coverage exacte/superset conservative avec epochs prouvés
repair borné réutilisant Transport/Common RAW/admission/Store existants
source-loss continuation uniquement sous TargetCoverage prouvée
snapshots gap/repair source-neutral et health gap-aware
``` ```
Reste hors de cette verticale et nécessite une tranche dédiée : Reste hors de cette verticale et nécessite une tranche dédiée :
```text ```text
sources EARLY / pre-execution sources EARLY / pre-execution
coverage-equivalence ou failover non-terminal entre sources campagne historique multi-source ou caller-paramétrable
repair historique multi-source
Worker multi-source -> Store live E2E avec environnement complet provisionné Worker multi-source -> Store live E2E avec environnement complet provisionné
``` ```

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md --> <!-- file: docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md -->
<!-- version: 99 --> <!-- version: 100 -->
# Séquence des releases fonctionnelles KSP # Séquence des releases fonctionnelles KSP

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,798 @@
<!-- file: docs/validation/031-V0_3_14_MULTI_SOURCE_GAP_REPAIR_HARDENING.md -->
<!-- version: 6 -->
# Validation v0.3.14 — gap repair / hardening multi-source RawTransaction
## 1. Rôle
Ce document enregistre les preuves réellement exécutées, les non-claims et les décisions de validation de `0.3.14`.
`pre.001` est uniquement le gate d'audit/brainstorming/sizing/planification demandé par `prompts/033-V0_3_14_START_PROMPT.md`. Aucun moteur de repair, aucune policy failover/degraded, aucune modification Transport/Config et aucun adapter EARLY n'est implémenté dans cette tranche.
## 2. Archive stable contrôlée
```text
archive : khadhroony-solana-project-v0.3.13.zip
SHA-256 : 253fa7ef5e0a6b2721da66fc3ea8dc7695d991d3a00bcc7d34396d9a32b65245
bytes : 8431253
entries : 2012
files : 1818
absolute entries : 0
parent traversal entries : 0
duplicate entries : 0
symlink entries : 0
workspace members : 21
crate manifests : 21
workspace.package.version : 0.3.13
edition : 2024
stable delta : deltas/0.3.13/rel.001.md
```
Artefacts interdits recherchés :
```text
Cargo.lock : absent
package-lock.json : absent
pnpm-lock.yaml : absent
yarn.lock : absent
rust-toolchain.toml : absent
.env : absent
target/ : absent
node_modules/ : absent
dist/ : absent
.git/ : absent
```
Résultat archive : PASS.
L'absence de `.git` empêche de vérifier localement l'objet du tag `v0.3.13`. L'identité stable interne et le delta `rel.001` sont présents et cohérents ; aucune équivalence cryptographique au tag n'est revendiquée.
## 3. Preuve opérateur stable fournie
Le journal opérateur fourni avec la base exécute sur `0.3.13` :
```text
cargo clean
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 --workspace --all-targets --all-features
cargo tree -p ksp-worker-raw-transaction-ingest-lib --edges normal
cargo tree -p ksp-worker-raw-transaction-ingest-lib -e features
cargo tree --duplicates
```
Les sorties fournies contiennent :
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
KSP workspace Rust rule audit: clean
Markdown table audit: clean (340 table(s), 855 file(s))
```
Agrégation des `130` lignes `test result` du journal :
```text
1 850 passed
0 failed
15 ignored
```
Ce gate qualifie la stable d'entrée. Les tests ignored restent ignored ; aucune preuve provider-gated n'est inventée.
## 4. Audits locaux de la base avant modification
Exécuté localement :
```bash
python3 scripts/audit_rust_workspace_rules.py
```
Résultat :
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
KSP workspace Rust rule audit: clean
```
Exécuté localement :
```bash
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas
```
Résultat :
```text
Markdown table audit: clean (340 table(s), 855 file(s))
```
`cargo`/`rustfmt` ne sont pas disponibles dans l'environnement d'assemblage courant. Aucune commande Cargo locale n'est déclarée PASS.
## 5. Audit des règles
Documents normatifs relus :
```text
RULES.md
docs/rules/RULES_GENERAL.md
docs/rules/RULES_KSP.md
docs/rules/RULES_RUST.md
docs/rules/RULES_DEPENDENCIES.md
docs/rules/RULES_DOCUMENTATION.md
docs/rules/FILE_CONTRACTS.md
docs/rules/VERSION_WORKFLOW.md
docs/rules/PROMPT_STRUCTURE.md
```
Scan supplémentaire des définitions normatives :
```text
489 définitions
489 identifiants uniques
0 doublon
```
Un scan générique de tous les textes trouve des headers absents dans quelques deltas historiques et un fichier de font/documentation, ainsi qu'une fin de ligne absente dans un JSON de capability. Ces constats ne sont pas assimilés à une violation nouvelle de la stable :
```text
les deltas historiques publiés sont immuables
JSON ne supporte pas les headers de commentaire
audit Rust officiel : clean
audit Markdown officiel : clean
```
Aucun ancien delta n'est réécrit par `pre.001`.
## 6. Frontières de dépendances contrôlées
Le manifest Worker stable contient exactement comme dépendances directes :
```text
ksp-core-lib
ksp-logging-lib
ksp-onchain-transport-lib
ksp-raw-transaction-lib
ksp-store-lib
ksp-worker-api
sha2
tokio
```
Absences confirmées :
```text
ksp-config-lib
ksp-job-backfill-lib
ksp-store-postgres-lib
reqwest
tokio-tungstenite
tonic
yellowstone-grpc-proto direct Worker
provider SDK
```
La séparation Worker/Job/Transport/Store est donc cohérente avec les règles à l'ouverture.
## 7. État de continuité interne audité
### 7.1 Yellowstone Transport
`YellowstoneGrpcSubscribeSnapshot` expose les compteurs de reconnect/replay et les slots sûrs. Sa documentation précise que `continuity_gap_count` est une preuve de rétention insuffisante lorsque `first_available` dépasse le replay demandé, pas une preuve qu'un événement filtré a réellement été perdu.
Résultat : conforme au besoin de distinction `replay_attempt != repair`.
### 7.2 Worker stable
`RawTransactionIngestProcessingFrontierReporter::observe_source_continuity` :
```text
rejette les régressions de compteurs
met à jour reconnect/replay/gap
rend source.continuity_gap_proven dès que continuity_gap_total augmente
```
Résultat : la terminalité conservative `0.3.13` est bien encore en place ; aucun repair non terminal caché n'existe.
### 7.3 WebSocket
L'acteur Transport incrémente le compteur de continuity gap lors d'une reconnexion physique. Il resubscribe les abonnements mais n'a pas de replay standard.
Les façades typées exposent `snapshot()` mais pas de `snapshot_source()` watch public, et les sources Worker Standard Logs/Standard Block/Helius ne consomment actuellement pas la snapshot WS. Une évolution Transport latest-value additive est donc planifiée avant le repair afin de ne pas dépendre d'une notification métier ultérieure pour voir le gap.
Résultat : reconnect WebSocket doit être traité comme détection de discontinuité potentielle, jamais comme preuve de couverture.
### 7.4 HTTP
Transport possède déjà :
```text
getSlot
getBlocks
getBlocksWithLimit
getBlock observed
getTransaction observed
```
Mais les ressources Worker ne garantissent pas toutes ces méthodes de la même manière :
```text
Yellowstone / Standard Logs / Helius Transaction : constructeur => getTransaction seulement
HTTP Block Polling : constructeur => getSlot + getBlocksWithLimit + getBlock
Standard Block : aucun pool HTTP attaché
fixtures hydration : rôles volontairement limités à get_transaction présents
```
Résultat : aucune nouvelle dépendance HTTP Worker n'est nécessaire pour le plan nominal, mais `pool HTTP présent` ne signifie jamais `repair HTTP disponible`. La capability de scan doit être détectée explicitement sur le rôle same-network réellement fourni.
## 8. Audit externe — Solana RPC/WebSocket
Références consultées le 11 septembre 2026 :
```text
https://solana.com/docs/rpc/http/getslot
https://solana.com/docs/rpc/http/getblocks
https://solana.com/docs/rpc/http/getblockswithlimit
https://solana.com/docs/rpc/http/getblock
https://solana.com/docs/rpc/http/gettransaction
https://solana.com/docs/rpc/websocket/logssubscribe
https://solana.com/docs/rpc/websocket/blocksubscribe
```
Constats :
```text
getBlocks/getBlocksWithLimit énumèrent des slots de blocs confirmés
plage maximale RPC documentée : 500 000 slots
getBlock peut retourner null
getTransaction peut retourner null
logsSubscribe transporte une référence/signature et non Common RAW
blockSubscribe reste instable et validator-gated
aucun replay historique WebSocket standard n'est documenté
```
Non-claim : les limites RPC ne sont pas reprises comme bornes Worker.
Point à requalifier en smoke : les pages localisées Solana affichent encore `maxSupportedTransactionVersion = 0` alors que la stable KSP porte Legacy/V0/V1. `pre.001` ne change pas le contrat stable sur cette divergence documentaire ; il conserve ce point comme gate provider/live distinct.
## 9. Audit externe — Yellowstone upstream
Références consultées :
```text
https://docs.rs/crate/yellowstone-grpc-proto/12.7.0
https://github.com/rpcpool/yellowstone-grpc/blob/master/yellowstone-grpc-proto/proto/geyser.proto
https://github.com/rpcpool/yellowstone-grpc/blob/master/CHANGELOG.md
```
Constats :
```text
KSP ^12.7 résout la version courante 12.7.0 publiée le 29 août 2026
SubscribeRequest possède from_slot
Geyser possède SubscribeReplayInfo
SubscribeReplayInfoResponse possède first_available
```
Risque upstream prouvé : le changelog du 22 juillet 2026 décrit un bug corrigé où un replay de filtre `blocks` acceptait `from_slot` mais ne livrait aucun `Message::Block` replayé, puis reprenait au live head avec un state gap.
Décision : une acceptation protocolaire de replay ne sera jamais suffisante comme critère `repaired`.
## 10. Audit providers courants
### 10.1 PublicNode
Référence :
```text
https://solana.publicnode.com/
```
Prouvé publiquement :
```text
Solana Mainnet RPC/WS/Yellowstone gRPC
Solana Testnet RPC/WS/Yellowstone gRPC
archive data available
```
Non prouvé dans une source primaire suffisamment précise :
```text
profondeur Yellowstone replay
first_available effectif
rétention du stream
auth exacte du profil KSP actuel
```
Statut replay `pre.001` : NON PROUVÉ / smoke requis.
### 10.2 OrbitFlare
Références :
```text
https://docs.orbitflare.com/data-streaming/yellowstone
https://orbitflare.com/products/solana-grpc
https://orbitflare.com/pricing
https://orbitflare.com/blog/developers/build-grpc-indexer
```
Prouvé publiquement :
```text
Yellowstone full-data streaming
Devnet gRPC sur les tiers Free/Developer courants
gRPC complet/shared payant pour les usages plus larges
exemple récent d'utilisation de from_slot pour reprendre depuis MAX(slot)
```
Non prouvé :
```text
profondeur exacte de replay du serveur KSP
SubscribeReplayInfo/first_available réellement activé sur le tier utilisé
équivalence du credential opérateur stable avec la documentation actuelle
```
Statut : candidat smoke Devnet, NON EXÉCUTÉ en `pre.001`.
### 10.3 Helius
Références :
```text
https://www.helius.dev/pricing
https://www.helius.dev/blog/laserstream-websockets
https://www.helius.dev/laserstream
```
Prouvé publiquement :
```text
transactionSubscribe WSS : Developer+
LaserStream gRPC Devnet : Developer+
LaserStream gRPC Mainnet : Business+
replay LaserStream annoncé jusqu'à 24 h
```
Décision : ne pas attribuer ce replay gRPC à `transactionSubscribe` WSS et ne pas ajouter LaserStream gRPC comme nouvelle source `0.3.14`.
### 10.4 QuickNode
Référence comparative :
```text
https://www.quicknode.com/guides/solana-development/tooling/solana-grpc/solana-grpc
```
La documentation du 3 septembre 2026 annonce `fromSlot` jusqu'à `3000` slots récents. QuickNode n'est pas configuré dans KSP ; aucune modification Config n'en découle.
## 11. Matrice de capabilities validée pour le plan
### Yellowstone
```text
gap detect : oui
native replay : oui si provider l'adresse
replay coverage proof : non par attempt seul
reference hydration : oui
block material : oui selon filtre
HTTP repair : conditionnel ; pool présent mais constructeur garantit seulement getTransaction
```
### Standard Logs
```text
gap detect : oui
native replay : non
coverage proof seule : non
reference hydration : oui
HTTP repair : conditionnel ; getTransaction seul est garanti par construction
```
### Standard Block
```text
gap detect : oui
native replay : non
block material : oui
coverage proof intervalle : pas sans preuve complémentaire
HTTP repair attaché : non ; capability run-wide possible via une autre ressource compatible
```
### Helius Transaction WSS
```text
gap detect : oui
native replay WSS : non prouvé
coverage proof seule : non
reference hydration : oui
HTTP repair : conditionnel ; getTransaction seul est garanti par construction
```
### HTTP Block Polling
```text
gap detect : oui
slot enumeration : oui
skipped proof : oui uniquement dans une fenêtre de discovery prouvée complète
block material : oui
HTTP repair : oui
```
Interprétation de coverage retenue : `HTTP Block Polling` et `Standard Block(All)` sont les deux scopes `FullLedgerTransactions` immédiatement prouvables par l'état stable. Les autres filtres restent des `ExactSourceScope` privés. Une source full-ledger continue peut couvrir canoniquement un scope plus étroit sans fabriquer l'observation manquante de la source perdue. À l'inverse, un scan full-block n'est pas autorisé à élargir un run composé uniquement de scopes filtrés.
## 12. Décisions pre.001
Décisions fermées :
```text
1. gap = intervalle de continuité run-local, pas liste présumée de transactions manquantes ;
2. gap privé lié à source/network/commitment/range/coverage requirement ;
3. reconnect, replay_attempt, replay_covered, repaired et unresolved sont distincts ;
4. replay accepté n'est jamais une preuve de repair ;
5. transaction-only source peut fournir du matériau mais pas prouver l'absence d'autres transactions ;
6. le repair vise la coverage des entités RawTransaction autorisées, pas la fabrication des observations provider manquantes ;
7. FullLedgerTransactions est un superset explicite ; un scope filtré reste exact/opaque tant qu'une relation plus forte n'est pas prouvée ;
8. aucune équivalence cross-family n'est déduite d'un provider, d'un hash ou de deux sockets Active ;
9. KnownReferences/getTransaction peut compléter du matériau connu mais ne ferme pas les références inconnues ;
10. un pool/role d'hydration garantit getTransaction seulement ; les méthodes de scan doivent être détectées séparément ;
11. preuve finale de range = fenêtre slot/block explicitement complète ou capability équivalente qualifiée ;
12. getBlocksWithLimit réussi ne ferme pas à lui seul une queue arbitraire car sa limite compte des blocs ;
13. skipped slot seulement dans une fenêtre discovery prouvée complète, jamais depuis silence de stream ;
14. getBlock null sur slot produit = unresolved tant qu'aucune autre preuve ne le ferme ;
15. getTransaction null = missing/unresolved pour la référence connue, avec fallback possible vers le bloc du slot ;
16. un scan full-block ne broadit pas silencieusement un scope filtré ;
17. tout matériau admis repasse par Common RAW, admission centrale et ksp-store-lib ;
18. aucun retry réseau parallèle dans Worker ;
19. toutes les sources restent attendues ; redundant est une relation de coverage dynamique, pas un flag provider statique ;
20. Healthy exige aucune lacune et toutes les sources attendues actives ;
21. Degraded est permis uniquement si coverage canonique reste prouvée malgré reconnect/perte ;
22. Unhealthy signifie coverage temporairement non prouvée pendant repair borné ; unresolved non récupérable fait Faulted ;
23. processing frontier et continuity frontier restent distincts ; les ranges sont inclusifs pour couvrir les pertes intra-slot ;
24. un gap WS sans borne slot sûre n'est pas inventé depuis un timestamp ; il reste unresolved/terminal si aucune autre capability ne le borne ;
25. la snapshot WS doit devenir observable latest-value sans attendre une notification métier, via une petite surface Transport additive ;
26. TargetCoverage est la réunion conservative des scopes configurés au même network/commitment ; aucune relation Confirmed/Finalized implicite ;
27. une source Transport terminale n'est jamais respawnée par le Worker ; Degraded durable exige que les sources restantes couvrent TargetCoverage pour la suite du run ;
28. EARLY est hors scope 0.3.14 ; aucune nouvelle dépendance n'est nécessaire ; Config reste inchangé en pre.001 et ne sera ouvert qu'en cas de besoin prouvé ;
29. 0.3.14 est maintenue sans rescission, mais le forecast est allongé jusqu'à pre.016 pour respecter le budget par tranche.
```
## 13. Bornes cibles à tester
```text
MAX_OPEN_REPAIR_GAPS = 64
MAX_REPAIR_RANGE_SLOTS = 4096
MAX_REPAIR_DISCOVERY_WINDOW_SLOTS = 512
MAX_REPAIR_BLOCK_FETCH_IN_FLIGHT = 4
MAX_REPAIR_ACTIVE_GAPS = 1
```
Ces valeurs doivent être matérialisées par `pre.002+` avec tests exacts aux limites. Elles ne sont pas encore du runtime en `pre.001`.
## 14. Threat model enregistré
À couvrir :
```text
reconnect storms
overlapping/adjacent gaps
stale redundant source
filter non-equivalence
replay truncation
replay accepted but empty
HTTP holes
skipped slots
getBlock null after produced-slot discovery
getTransaction null
duplicate repair/live
content disagreement
slow Store
admission backpressure
hydration fanout/coalescence
stop/fault pendant replay/scan/hydration/persistence
counter exhaustion
late snapshot publication
secret/provider/filter/signature leakage
```
## 15. Smokes accessibles / non accessibles en pre.001
```text
Solana HTTP Devnet : accessible en principe, non exécuté dans cette tranche de planification
Solana WebSocket Devnet : déjà qualifié sur stable ; non rejoué en pre.001
OrbitFlare Yellowstone Devnet replay : credential requis, NON EXÉCUTÉ
PublicNode Yellowstone Mainnet/Testnet replay : auth/replay à qualifier, NON EXÉCUTÉ
Helius transactionSubscribe : tier/API key requis, NON EXÉCUTÉ
Worker repair -> Store end-to-end : moteur non implémenté, NON EXÉCUTÉ
```
Aucun smoke n'est présenté comme PASS sans exécution réelle.
## 16. Sizing
Mesuré sur la stable :
```text
Worker src : 9 fichiers / 6 317 lignes
Worker tests : 5 fichiers / 2 725 lignes
Worker unit_tests : 6 fichiers / 5 523 lignes
Transport src : 33 fichiers / 22 764 lignes
```
Décision : scope maintenu dans `0.3.14`, mais le forecast initial est allongé de `pre.014` à `pre.016`. L'audit a isolé deux responsabilités qui ne doivent pas être mélangées : observabilité WS latest-value avant repair, puis replay natif et coverage redondante en tranches séparées. Les cinq familles et les primitives Transport restent la base ; les extensions suivantes rendraient le scope trop large et sont interdites dans cette release :
```text
EARLY/Jetstream/shreds
nouveau provider Config
provider SDK
nouveau client Transport parallèle
campagne historique caller-driven
checkpoint durable Worker/Job
```
## 17. Modifications de pre.001
```text
Cargo.toml
header 565 -> 566
workspace.package.version 0.3.13 -> 0.3.14-pre.1
docs/plans/035-V0_3_14_MULTI_SOURCE_GAP_REPAIR_HARDENING_PLAN.md
ajouté
docs/validation/031-V0_3_14_MULTI_SOURCE_GAP_REPAIR_HARDENING.md
ajouté
deltas/0.3.14/pre.001.md
ajouté
```
Aucun code Rust, Config, test, dépendance, feature, README/USAGE, architecture, CHANGELOG, ROADMAP ou prompt n'est modifié.
## 18. Gate local post-modification
Exécuté après application effective de `pre.001` :
```text
General Rust rule audit : clean
Rust export completeness audit : 0 candidate(s)
KSP workspace Rust rule audit : clean
Markdown table audit : clean (340 tables / 858 files)
rule definitions : 489 définitions / 489 IDs uniques / 0 doublon
comparaison archive stable -> worktree : 3 ajouts / 1 modification / 0 suppression
artefacts générés après audit : scripts/__pycache__ supprimé avant packaging
Cargo fmt/check : NON EXÉCUTÉ LOCAL — cargo absent du sandbox
```
La comparaison avec le ZIP stable confirme que les seuls fichiers de livraison sont :
```text
Cargo.toml
docs/plans/035-V0_3_14_MULTI_SOURCE_GAP_REPAIR_HARDENING_PLAN.md
docs/validation/031-V0_3_14_MULTI_SOURCE_GAP_REPAIR_HARDENING.md
deltas/0.3.14/pre.001.md
```
Le statut Cargo post-bump reste volontairement non revendiqué. Le journal opérateur fourni qualifie la stable `0.3.13`, pas `0.3.14-pre.1`.
## 19. Gate opérateur requis avant pre.002
```bash
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/0.3.14
cargo check --workspace
```
`pre.001` ne modifie pas de Rust ; un Clippy/test workspace exhaustif n'est pas nécessaire pour prouver le contenu de cette tranche de planification, mais le gate stable complet fourni reste la baseline. Si le gate post-bump révèle un défaut, ouvrir un fix rattaché à `pre.001` avant `pre.002`.
## 20. Gate d'entrée de `pre.014` — preuve opérateur `pre.013-fix.001`
Le 12 septembre 2026, le gate communiqué sur `0.3.14-pre.13.fix.1` est vert pour toutes les commandes effectivement exécutées :
```text
cargo fmt --all / --check : PASS
General Rust rule audit : clean
Rust export completeness audit : 0 candidate(s)
KSP workspace Rust rule audit : clean
Markdown table audit : clean (340 tables / 881 files)
cargo check --workspace : PASS
cargo clippy --workspace --all-targets --all-features -- -D warnings : PASS
Worker unit : 152/152 PASS
Worker cross-layer completeness : 12/12 PASS
Worker dependency boundary : 21/21 PASS
Worker hardening : 38/38 PASS
Worker public API : 22/22 PASS
Worker release completeness : 15/15 PASS
```
Ce gate qualifie la tranche `pre.013-fix.001` et constitue la baseline d'entrée de `pre.014`. Il ne contient pas les sorties suivantes et elles ne sont donc pas déclarées PASS ici :
```text
cargo test --workspace --all-targets --all-features
cargo tree -p ksp-worker-raw-transaction-ingest-lib --edges normal
cargo tree -p ksp-worker-raw-transaction-ingest-lib -e features
cargo tree --duplicates
smoke Solana HTTP Devnet ignoré/opt-in
smoke Solana WebSocket Devnet ignoré/opt-in
```
## 21. Responsabilité de `pre.014` — gate technique/live final
`pre.014` n'ajoute aucun comportement métier. Sa responsabilité est de fermer le gate technique/live de `0.3.14` sur l'arbre final après hardening :
```text
workspace complet
Clippy strict all-targets/all-features
tests workspace all-targets/all-features
graphe Worker normal
graphe Worker features
inventaire cargo tree --duplicates
smokes keyless HTTP/WebSocket Devnet réellement exécutés
qualification explicite des smokes provider/resource-gated non exécutés
```
Une sortie non vide de `cargo tree --duplicates` est un inventaire multi-version ; elle n'est pas requalifiée artificiellement en défaut tant que les dependency-firewall et canaris cross-layer restent verts.
Les tests `#[ignore]` compilés par le gate workspace ne constituent jamais une preuve live. Les smokes live sont exécutés séparément avec `--ignored --nocapture`.
## 22. Gate déterministe final à exécuter sur `0.3.14-pre.14`
```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 --workspace --all-targets --all-features
cargo tree -p ksp-worker-raw-transaction-ingest-lib --edges normal
cargo tree -p ksp-worker-raw-transaction-ingest-lib -e features
cargo tree --duplicates
```
Le gate n'est clos que si toutes les commandes déterministes ci-dessus terminent sans erreur. Le nombre de versions dupliquées éventuellement listées par Cargo est enregistré comme inventaire et ne devient bloquant que s'il révèle une violation effective des frontières de dépendances du projet.
## 23. Smokes live accessibles à exécuter séparément
Les deux smokes Transport sans secret opérateur requis sont :
```bash
cargo test -p ksp-onchain-transport-lib --test transport_devnet_smoke -- --ignored --nocapture
cargo test -p ksp-onchain-transport-lib --test websocket_devnet_smoke -- --ignored --nocapture
```
Ils doivent être réellement exécutés sur `0.3.14-pre.14` avant de les déclarer PASS. Leur qualification antérieure sur `0.3.13` reste une preuve historique et n'est pas substituée au gate final de `0.3.14`.
Smokes restant conditionnels à un environnement externe :
```text
OrbitFlare Yellowstone Devnet : credential requis -> NON EXÉCUTÉ sauf provisionnement opérateur
PublicNode Yellowstone : auth/capability replay requise -> NON EXÉCUTÉ sauf provisionnement opérateur
Helius transactionSubscribe : API key/tier requis -> NON EXÉCUTÉ sauf provisionnement opérateur
Postgres live Store : instance/configuration externe requise -> NON EXÉCUTÉ sauf provisionnement opérateur
Worker repair -> Store live end-to-end : aucune preuve live dédiée n'est revendiquée sans environnement explicitement provisionné
```
Aucun de ces états `NON EXÉCUTÉ` n'est transformé en PASS par compilation, test unitaire, fixture ou résultat historique.
## 24. Modifications de `pre.014`
```text
Cargo.toml
header 589 -> 590
workspace.package.version 0.3.14-pre.13.fix.1 -> 0.3.14-pre.14
docs/validation/031-V0_3_14_MULTI_SOURCE_GAP_REPAIR_HARDENING.md
enregistrement du gate d'entrée réellement reçu
matrice déterministe finale
smokes keyless finaux et non-claims provider/resource-gated
deltas/0.3.14/pre.014.md
ajouté
```
Aucun fichier Rust, test, dépendance, feature, README/USAGE, architecture, CHANGELOG, ROADMAP ou prompt suivant n'est modifié par cette tranche.
## 25. Statut local d'assemblage `pre.014`
Audits statiques réellement exécutés après modification :
```text
General Rust rule audit : clean
Rust export completeness audit : 0 candidate(s)
KSP workspace Rust rule audit : clean
Markdown table audit : clean (340 tables / 882 files)
```
Le toolchain Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Aucun PASS Cargo post-bump n'est donc revendiqué localement.
Le gate Cargo complet et les smokes live de `pre.014` restent `À EXÉCUTER` par l'opérateur avant `pre.015`.
## 26. Gate opérateur final de `pre.014`
Gate exécuté le **12 septembre 2026 à 21:07 +02:00** sur `workspace.package.version = 0.3.14-pre.14` :
```text
cargo fmt --all : PASS
cargo fmt --all -- --check : PASS
General Rust rule audit : clean
Rust export completeness audit : 0 candidate(s)
KSP workspace Rust rule audit : clean
Markdown table audit : clean (340 tables / 882 files)
cargo check --workspace : PASS
cargo clippy --workspace --all-targets --all-features -- -D warnings : PASS
cargo test --workspace --all-targets --all-features : PASS
workspace tests : 1931 passed / 0 failed / 15 ignored
cargo tree -p ksp-worker-raw-transaction-ingest-lib --edges normal : exécuté
cargo tree -p ksp-worker-raw-transaction-ingest-lib -e features : exécuté
cargo tree --duplicates : exécuté
```
L'arbre normal confirme que le Worker conserve les dépendances directes documentées :
```text
ksp-core-lib
ksp-logging-lib
ksp-onchain-transport-lib
ksp-raw-transaction-lib
ksp-store-lib
ksp-worker-api
sha2
tokio
```
La commande `cargo tree --duplicates` est un inventaire des duplications transitives du workspace, pas une exigence de zéro duplication ; son exécution n'a révélé aucune nouvelle dépendance directe introduite par `0.3.14`.
### 26.1 Smokes keyless réellement exécutés
HTTP Devnet :
```text
cargo test -p ksp-onchain-transport-lib --test transport_devnet_smoke -- --ignored --nocapture
1 passed / 0 failed
```
WebSocket Devnet :
```text
cargo test -p ksp-onchain-transport-lib --test websocket_devnet_smoke -- --ignored --nocapture
1 passed / 0 failed
```
Ces deux smokes prouvent les canaris Transport keyless accessibles. Ils ne constituent pas un smoke Worker -> Store end-to-end.
### 26.2 Non-claims finaux
Restent explicitement `NON EXÉCUTÉ` faute de provisionnement opérateur dans ce gate :
```text
OrbitFlare Yellowstone Devnet live/replay
PublicNode Yellowstone Mainnet/Testnet live/replay
Helius transactionSubscribe live
PostgreSQL live Store
Worker gap repair -> Store live end-to-end
```
Les tests ignored correspondants présents dans le workspace ne sont pas requalifiés en PASS.
## 27. Réconciliation documentaire `pre.015`
`pre.015` ne modifie aucun Rust, test, dépendance, feature, Config, schema ou comportement runtime. Elle réconcilie seulement les documents durables avec les preuves `0.3.14` effectivement livrées :
```text
README/USAGE Worker : source-loss conditionnel, gaps/repair, health et snapshots publics
architecture : composant/graph/Worker-vs-Job/RawTransaction acquisition
functional release sequence : rôle réel de 0.3.14
plan 035 : forecast exécuté et invariants finaux
validation 031 : gate workspace + smokes keyless réellement exécutés
```
`CHANGELOG.md`, `ROADMAP.md` et le prompt `0.3.15` restent volontairement inchangés jusqu'à `pre.016`.

View File

@@ -0,0 +1,972 @@
<!-- file: prompts/034-V0_3_15_START_PROMPT.md -->
<!-- version: 1 -->
# Prompt de démarrage `0.3.15` — Raw Transaction Ingest Desk / composition et supervision de routes live
## 1. Identité de la release et base exacte requise
Ouvrir **uniquement** `0.3.15` depuis la release stable/taggée :
```text
v0.3.14
```
La base fournie par l'opérateur est autoritaire sur les souvenirs, snippets, anciennes archives et deltas intermédiaires. Avant toute modification, vérifier au minimum :
```text
workspace.package.version = 0.3.14
deltas/0.3.14/rel.001.md présent
ksp-worker-raw-transaction-ingest-lib stable et consommable depuis sa crate root
ksp-config-lib possède la résolution Transport HTTP / WS / Yellowstone gRPC
ksp-onchain-transport-lib possède les façades réellement utilisées par le Worker
ksp-store-lib reste l'unique façade Store du consumer
cinq familles live Worker préservées : Yellowstone / Standard Logs / Standard Block / Helius Transaction / HTTP Block Polling
gaps run-local, TargetCoverage, health, fairness et shutdown 0.3.14 présents
snapshots publics gap/repair source-neutral présents
aucun edge Worker -> Config / Job Backfill / backend Store physique
```
Release ouverte :
```text
0.3.15
```
Première livraison attendue :
```text
0.3.15-pre.001
```
`pre.001` est obligatoirement un gate de **lecture + audit de composition Config/Transport/Store/Worker + audit du gabarit Desk KSP + brainstorming + sizing + planification**. Il est interdit de commencer directement par l'UI, par des commandes Tauri Start/Stop ou par la copie d'une application Desk existante avant fermeture de ce gate.
## 2. Mission et résultat attendu
Créer :
```text
crates/ksp-app-raw-transaction-ingest-desk
```
comme application Tauri KSP spécialisée dans la **composition, le démarrage, l'arrêt et la supervision de routes live `RawTransaction` réellement supportées par la configuration et le Worker finalisé**.
Le Desk doit permettre à l'opérateur de :
```text
charger/utiliser la configuration KSP
choisir le réseau logique disponible
voir les routes live composables pour ce réseau
comprendre pourquoi une route non composable est désactivée
sélectionner une ou plusieurs routes composables
démarrer une instance Worker par route sélectionnée
arrêter chaque Worker proprement
observer lifecycle / health / activité / backpressure / reconnect / replay / gaps / repair via les snapshots Worker
vérifier que les données convergent réellement vers le même Store RawTransaction
```
Le Desk ne devient pas une seconde implémentation de l'acquisition. La chaîne d'ownership cible est :
```text
Frontend Tauri
-> route_id / action Start-Stop / DTO sûrs
-> backend Rust de ksp-app-raw-transaction-ingest-desk
-> ksp-config-lib
-> ksp-onchain-transport-lib
-> ksp-store-lib
-> ksp-worker-raw-transaction-ingest-lib
-> ksp-raw-transaction-lib
-> même Store RawTransaction
```
### 2.1 Règle fondamentale : route != source != endpoint
Une **route** est une stratégie d'acquisition complète. Elle peut nécessiter plusieurs ressources/capacités techniques pour devenir exécutable.
Exemples conceptuels, à réauditer contre la base réelle :
```text
Yellowstone transaction stream + HTTP getTransaction hydration
Standard WS logsSubscribe + HTTP getTransaction hydration
Standard WS blockSubscribe RAW-direct
Helius transactionSubscribe + HTTP getTransaction hydration
HTTP Block Polling RAW-direct
```
Une route n'est pas sélectionnable parce qu'un seul endpoint portant le bon provider existe. Elle est sélectionnable uniquement si **toutes ses capacités obligatoires** peuvent être composées depuis la Config pour le même réseau logique.
Exemple :
```text
route exige Yellowstone gRPC + HTTP getTransaction
gRPC disponible = oui
HTTP compatible = non
=> route NON SÉLECTIONNABLE
```
Autre exemple :
```text
endpoint Helius disponible uniquement comme WS standard
route Standard WS Logs compatible = potentiellement sélectionnable
route exigeant Helius transactionSubscribe = NON sélectionnable si cette capability spécifique n'est pas disponible
```
Il est interdit de déduire une capability uniquement à partir du nom du provider.
### 2.2 Une instance Worker par route sélectionnée
Décision acquise :
```text
1 route sélectionnée = 1 instance ksp-worker-raw-transaction-ingest-lib
N routes sélectionnées = N Workers indépendants
```
Ce n'est **pas** un Worker par source physique.
Une même route peut composer plusieurs ressources internes nécessaires à sa stratégie : gRPC + HTTP, WS + HTTP, ou d'autres ressources réellement requises par les contrats Worker. Toutes ces ressources appartiennent au Worker de cette route.
Plusieurs Workers de routes différentes peuvent écrire vers le même Store `RawTransaction`. Ils restent indépendants pour :
```text
lifecycle
gaps / TargetCoverage
health
repair
shutdown
```
Aucun partage implicite de gap ledger ou de preuve de coverage entre Workers distincts n'est introduit. La convergence durable passe par les contrats Store/idempotence/observations existants.
### 2.3 Composable avant Start, opérationnelle après Start
Deux niveaux sont obligatoirement distingués :
```text
COMPOSABLE
toutes les capacités/configurations requises existent et sont cohérentes
décision déterministe à partir de Config et des contrats connus
OPÉRATIONNELLE
les ressources sélectionnées fonctionnent réellement au runtime
preuve obtenue lors du Start / runtime Worker-Transport-Store
```
La liste des routes ne doit pas lancer des probes réseau arbitraires pour fabriquer artificiellement une disponibilité. Le backend Desk détermine d'abord la composabilité à partir de la configuration résolue. Au `Start`, le Worker et les façades Transport/Store valident réellement les ressources, les réseaux, les connexions et les capacités runtime nécessaires.
Une route peut donc rester visible comme `Configured/Composable` puis passer `Starting -> Running` ou `Starting -> Faulted` selon le runtime réel.
## 3. Sources de vérité internes obligatoires — ordre de lecture
### 3.1 Gouvernance générale
Lire intégralement, dans cet ordre :
```text
RULES.md
ROADMAP.md
CHANGELOG.md
docs/000-README.md
docs/rules/RULES_GENERAL.md
docs/rules/RULES_KSP.md
docs/rules/RULES_RUST.md
docs/rules/RULES_DEPENDENCIES.md
docs/rules/RULES_DOCUMENTATION.md
docs/rules/FILE_CONTRACTS.md
docs/rules/VERSION_WORKFLOW.md
docs/rules/PROMPT_STRUCTURE.md
```
Le prompt complète ces règles ; il ne les remplace pas.
Rappels bloquants :
```text
Rust 2024
unsafe interdit
unwrap / expect / panic interdits selon les règles KSP
? interdit en production
retours explicites ; clippy::implicit_return deny
#![warn(missing_docs)]
#![deny(unreachable_pub)]
#![forbid(unsafe_code)]
pas de pub mod
pub/pub(crate) partagés reexportés via crate root
accès partagés via crate::Item, y compris intra-crate
item strictement module-local => private
unit tests sous unit_tests/
integration tests sous tests/
```
Après toute modification Rust :
```bash
cargo fmt --all
python3 scripts/audit_rust_workspace_rules.py
cargo check --workspace
cargo clippy --workspace --all-targets
```
Pour tout Markdown touché :
```bash
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas
```
Une commande non exécutée n'est jamais déclarée PASS.
### 3.2 Handoff stable `0.3.14`
Lire intégralement :
```text
prompts/033-V0_3_14_START_PROMPT.md
docs/plans/035-V0_3_14_MULTI_SOURCE_GAP_REPAIR_HARDENING_PLAN.md
docs/validation/031-V0_3_14_MULTI_SOURCE_GAP_REPAIR_HARDENING.md
deltas/0.3.14/pre.014.md
deltas/0.3.14/pre.015.md
deltas/0.3.14/pre.016.md
deltas/0.3.14/rel.001.md
crates/ksp-worker-raw-transaction-ingest-lib/README.md
crates/ksp-worker-raw-transaction-ingest-lib/USAGE.md
```
Puis auditer le code réel de :
```text
crates/ksp-worker-raw-transaction-ingest-lib/src/
crates/ksp-worker-raw-transaction-ingest-lib/tests/
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/
```
`0.3.15` consomme le Worker finalisé ; il ne le recode pas dans l'application.
### 3.3 Config et composition
Lire :
```text
crates/ksp-config-lib/README.md
crates/ksp-config-lib/USAGE.md
crates/ksp-config-lib/src/
config/std.transport.json
config/examples/std.transport.example.json
config/schemas/std.transport.schema.json
config/std.store.json
config/schemas/std.store.schema.json
config/composite.ksp-app-backfill-desk.json
config/composite.ksp-app-store-desk.json
.env.example
```
Inventorier réellement :
```text
profils HTTP configurés
rôles HTTP et request_kinds
profils WS et protocol kind
profils Yellowstone gRPC
réseaux logiques
credentials/secrets requis
Store targets et réseau associé
mécanismes de composite/profil sélectionné
```
Ne pas confondre un exemple sous `config/examples/` avec un profil réellement actif dans `config/std.transport.json`.
### 3.4 Transport et Worker runtime resources
Lire :
```text
crates/ksp-onchain-transport-lib/README.md
crates/ksp-onchain-transport-lib/USAGE.md
crates/ksp-onchain-transport-lib/src/
crates/ksp-onchain-transport-lib/tests/
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
crates/ksp-worker-raw-transaction-ingest-lib/src/snapshot.rs
```
Auditer précisément les constructeurs publics actuellement disponibles :
```text
RawTransactionIngestRuntimeResources
RawTransactionIngestYellowstoneSource
RawTransactionIngestStandardLogsSource
RawTransactionIngestStandardBlockSource
RawTransactionIngestHeliusTransactionSource
RawTransactionIngestHttpBlockPollingSource
RawTransactionIngestWorker::start_with_runtime_resources
RawTransactionIngestHandle
RawTransactionIngestSnapshotSource
```
Le résultat de cet audit doit montrer, route par route, quelles capacités Transport/Config sont nécessaires pour construire le Worker sans bypass.
### 3.5 Store
Lire :
```text
crates/ksp-store-lib/README.md
crates/ksp-store-lib/USAGE.md
crates/ksp-store-lib/src/
crates/ksp-store-api/src/
```
Le Desk dépend de `ksp-store-lib`, jamais de `ksp-store-postgres-lib` ni d'un client SQL.
Un run Worker et le Store doivent porter exactement le même réseau logique. `mainnet` reste l'identité KSP canonique ; `mainnet-beta` est uniquement un alias historique/externe lorsqu'il est accepté par une lower-layer.
### 3.6 Gabarit Desktop KSP
Le Desk doit reprendre le gabarit **KSP actuel**, jamais une application historique externe.
Lire au minimum :
```text
crates/ksp-app-config-desk/
crates/ksp-app-wallet-desk/
crates/ksp-app-solprices-desk/
crates/ksp-app-backfill-desk/
crates/ksp-app-store-desk/
```
Préserver le pattern déjà décidé :
```text
même splash KSP, seul le titre change
mêmes fonts
même style général
même discipline frontend/backend Tauri
tracing TypeScript des clics, boutons, tabs, filtres, refresh et IPC
aucun secret/endpoints sensibles dans les logs frontend
```
Le `package.json` doit au minimum partir du socle courant des Desk KSP, incluant lorsque toujours présent après audit :
```text
@fltsci/tauri-plugin-tracing
@fortawesome/fontawesome-free
@tauri-apps/api
bootstrap
resize-observer-polyfill
simplebar
```
et les mêmes devDependencies de base compatibles que les Desk KSP actuelles. Ne pas lancer une mise à niveau opportuniste du workspace frontend sans justification de `pre.001`.
## 4. Sources externes à réauditer en `pre.001`
La Desk elle-même ne doit pas dépendre de connaissances provider codées depuis une page marketing. Néanmoins, si la construction d'une route dépend d'une capability Transport/provider dont la sémantique ou la disponibilité a changé, réauditer les sources primaires réellement actuelles :
```text
Solana JSON-RPC / WebSocket pour méthodes/subscriptions utilisées
Yellowstone gRPC upstream pour Subscribe / replay réellement exposé
Helius pour la distinction standard WS vs transactionSubscribe/provider-specific lorsque nécessaire
PublicNode / OrbitFlare uniquement si un profil actif et un smoke réel les utilisent
Tauri 2 / plugins réellement employés si une adaptation desktop est nécessaire
```
Les résultats externes servent à qualifier les capabilities. Ils ne doivent jamais rendre sélectionnable une route que la Config locale ne peut pas composer.
## 5. État validé à préserver depuis `v0.3.14`
### 5.1 Worker
Préserver :
```text
une admission/persistence Common RAW unique par Worker
five live source families existantes
hydratation globale/coalescence interne au Worker
idempotence et content conflict explicite
processing frontier distinct de continuity frontier
gaps run-local bornés
TargetCoverage conservative
reconnect != replay_attempt != replay_covered != repaired
fairness nominal/repair
shutdown/drain/abort+join borné
snapshots source-neutral avec gap observability
aucun Backfill automatique
```
### 5.2 Ownership
Préserver :
```text
Config possède profiles/endpoints/secrets
Transport possède clients/sessions/retry/reconnect/resubscribe/replay natif
Worker possède logique continue RawTransaction, continuity et repair du run
Store possède persistence/idempotence/observations
Desk compose et supervise ; il ne réimplémente aucune de ces couches
```
### 5.3 Producteurs indépendants
Préserver :
```text
ksp-worker-raw-transaction-ingest-lib = acquisition continue
ksp-job-backfill-lib = acquisition historique bornée/paramétrée
Worker -X-> Backfill
Backfill -X-> Worker
```
`0.3.15` n'introduit aucune orchestration Backfill automatique depuis la Desk ingest.
## 6. Décisions acquises — ne pas redébattre sans contradiction réelle
```text
release : 0.3.15
application : ksp-app-raw-transaction-ingest-desk
gabarit : Desk KSP courant
frontend : UI/DTO/actions sûres uniquement
backend Rust Tauri : composition Config + Transport + Store + Worker
route != source != endpoint
route = stratégie complète + ensemble de capabilities obligatoires
route sélectionnable uniquement si toutes ses requirements sont composables depuis Config
provider name seul != capability
composabilité Config != opérabilité runtime
validation réseau/connexion/capabilities réelles au Start
1 route sélectionnée = 1 Worker
pas 1 Worker par source physique
une route peut nécessiter plusieurs ressources Transport internes
plusieurs Workers/Routes peuvent écrire dans le même Store
pas de partage implicite des gaps/TargetCoverage entre Workers distincts
endpoints/secrets/handles restent Rust-only et hors IPC
Store via ksp-store-lib uniquement
aucun SQL/backend physique
aucune logique Backfill
```
## 7. Questions réellement ouvertes à trancher pendant `pre.001`
### 7.1 Catalogue exact des routes V1
Construire une matrice à partir des contrats réellement existants :
```text
route_id stable
objectif de la route
network admissible
capabilities obligatoires
capabilities optionnelles
ressources Worker construites
source family interne
RAW-direct ou reference-bearing
hydration requirement
repair capabilities internes
raison de non-composabilité projetable en DTO sûr
```
Le catalogue V1 doit être borné aux routes réellement constructibles sans bypass.
### 7.2 Lieu du catalogue de routes
Auditer si le catalogue reste légitimement app-owned dans le backend Desk ou si un petit contrat réutilisable manque dans une lower-layer existante.
Règle :
```text
ne pas créer une nouvelle crate intermédiaire par défaut
ne pas déplacer de logique d'ingestion vers l'app
si une extension lower-layer minimale est indispensable, la faire dans la crate propriétaire pendant 0.3.15 avec tests dédiés
```
### 7.3 Projection de capabilities Config
Décider comment obtenir une vue déterministe et sûre des capabilities configurées sans exposer :
```text
URL
credential
secret metadata
header sensible
client Transport
Store URI
```
Si les APIs publiques Config/Transport ne permettent pas de déterminer proprement la composabilité d'une route, identifier l'extension minimale backend-neutral au lieu de parser arbitrairement le JSON Config depuis le frontend.
### 7.4 Sélection réseau
Décider l'UX exacte : réseau explicitement choisi parmi les configurations disponibles, ou réseau dérivé d'un composite/Store target sélectionné.
Dans tous les cas :
```text
route.network == Worker.settings.network == Store.network
```
est obligatoire avant Start.
### 7.5 Multiples compositions pour une même route
Si plusieurs endpoints/rôles peuvent satisfaire une même route, décider avant UI lourde si :
```text
Config/Transport choisit automatiquement selon priorités existantes
ou
le Desk expose plusieurs compositions logiques sûres
```
Le frontend ne doit pas devenir un éditeur de connexions physiques et ne reçoit pas les URLs.
### 7.6 Lifecycle de plusieurs Workers
Définir l'état applicatif pour :
```text
0..N routes sélectionnées
0..N Workers actifs
Start individuel / Stop individuel
Start sélection globale éventuellement
fermeture application
route déjà Running
Start concurrent de deux routes
fault d'une route sans arrêt implicite des autres
Store partagé
```
Chaque handle doit rester associé de façon non ambiguë à un `route_id` applicatif sûr.
### 7.7 Validation finale réelle
Planifier les scénarios live réellement accessibles. Le Desk doit servir de validation système des couches existantes, mais aucun provider non provisionné ne reçoit un faux PASS.
## 8. Objectifs et livrables de `0.3.15`
À la clôture, viser :
```text
crate Tauri ksp-app-raw-transaction-ingest-desk intégrée au workspace
composite/config app dédié si réellement nécessaire
inventaire de routes source-neutral et borné
projection selectable / unavailable + raison sûre
sélection réseau cohérente
sélection d'une ou plusieurs routes
1 Worker réel par route sélectionnée
construction réelle des RawTransactionIngestRuntimeResources nécessaires
Start / Stop réels via ksp-worker-raw-transaction-ingest-lib
Store partagé via ksp-store-lib
snapshots Worker projetés vers l'UI sans données sensibles
lifecycle/health/activity/backpressure/reconnect/replay/gaps/repair visibles
fermeture application arrêtant/joinant proprement les Workers détenus
tracing frontend KSP
README/USAGE de l'application
plan + validation de release
gates Rust/frontend/Tauri complets
smokes live accessibles documentés
```
Une route marquée sélectionnable par l'UI doit être réellement constructible depuis la Config résolue ; une route incomplète reste désactivée avec une raison sûre.
## 9. Hors périmètre explicite
```text
nouveau moteur Worker
nouvelle campagne historique
extension multi-stratégie Backfill 0.3.16
Worker par endpoint/source physique
partage de gap ledger entre Workers distincts
orchestrateur global de failover cross-Worker
scheduler durable de Workers
persistance des sélections/runs au-delà de ce que pre.001 décide explicitement
SQL/backend Store direct
lecture directe .env depuis l'app
saisie/édition de secrets dans le frontend
éditeur arbitraire d'endpoints provider
exposition URL/token/header/client handle via IPC
réimplémentation retry/reconnect/replay/hydration/discovery/persistence dans la Desk
nouveau provider SDK sans besoin prouvé
```
## 10. Contraintes sécurité, API et architecture spécifiques
### 10.1 IPC
Le frontend peut connaître des identifiants logiques sûrs, par exemple :
```text
route_id
network
label
route family
selectable
unavailable reason code
Worker lifecycle/health
compteurs/snapshots déjà source-neutral
```
Il ne doit pas recevoir :
```text
endpoint URL
API key/token
secret metadata
Store URI
Transport client/session
source key privée
transaction payload brut par simple monitoring
error text provider arbitraire
```
### 10.2 Availability
La disponibilité affichée doit distinguer clairement :
```text
Unavailable / Not composable
Configured / Composable
Starting
Running
Stopping
Stopped
Faulted
```
Ne pas afficher `Running` ou `Ready` à partir de la seule présence d'une configuration.
### 10.3 Composition et validation
La composition affichée peut être calculée sans I/O réseau. Avant démarrage, le backend doit **revalider** la Config et reconstruire les ressources ; il ne fait jamais confiance à une sélection frontend obsolète.
Le Start doit échouer proprement si la configuration a changé ou si une ressource n'est plus valide.
### 10.4 Plusieurs Workers, même Store
Les routes actives écrivent dans le même Store logique lorsque leur réseau correspond. Les règles existantes de canonical identity, idempotence, observation et content conflict restent autoritaires.
Le Desk ne choisit jamais un « provider gagnant » et ne fusionne pas lui-même des RawTransactions.
## 11. Première mission `0.3.15-pre.001` — gate obligatoire
`pre.001` doit rester essentiellement audit/planification. Produire avant tout scaffold lourd :
### 11.1 Inventaire réel
```text
API publique Worker exacte
constructeurs RuntimeResources exacts
capabilities exigées par chaque famille Worker
APIs Config disponibles pour HTTP/WS/gRPC/Store
profils réellement committed vs exemples uniquement
mécanismes de rôles/priorités HTTP
kinds WS et protocoles gRPC
pattern de composition du Backfill Desk
pattern lifecycle/handle/snapshot dans les apps existantes
socle Tauri/TS/npm courant des Desk
```
### 11.2 Matrice route/capability
Produire une table complète :
```text
Route
Network
Required capability
Optional capability
Config evidence
Worker constructor/resource
Composable aujourd'hui ?
Missing contract éventuel
Live-testable ?
```
Exiger au moins l'analyse de :
```text
Yellowstone + HTTP hydration
Standard Logs + HTTP hydration
Standard Block direct
Helius transactionSubscribe + HTTP hydration
HTTP Block Polling
```
et distinguer explicitement une utilisation Helius en WS standard d'une route Helius `transactionSubscribe` provider-specific.
### 11.3 Audit gap
Classer chaque gap :
```text
aucun gap
adaptation app seulement
extension Config minimale
extension Transport minimale
extension Worker minimale
split de release nécessaire
```
Une extension lower-layer réellement nécessaire peut appartenir à `0.3.15`, mais elle doit rester minimale, propriétaire de sa sémantique et testée avant l'UI qui la consomme.
### 11.4 Gabarit et UX
Produire :
```text
screen map
route list mock contract
states selectable/unavailable/running/faulted
Start/Stop flow
snapshot mapping UI
IPC DTO matrix
frontend tracing matrix
```
sans encore implémenter toutes les vues.
### 11.5 Sizing
Chaque tranche intermédiaire doit viser environ 1520 minutes de travail effectif. Si une extension Config/Transport/Worker ou l'UI prévue rend la session trop grande, rescinder `0.3.15` avant l'implémentation lourde.
### 11.6 Livrables `pre.001`
Préparer au minimum :
```text
docs/plans/036-V0_3_15_RAW_TRANSACTION_INGEST_DESK_PLAN.md
docs/validation/032-V0_3_15_RAW_TRANSACTION_INGEST_DESK.md
deltas/0.3.15/pre.001.md
```
Le plan doit fixer la prévision souple réelle après audit, et non recopier mécaniquement celle du présent prompt.
## 12. Prévision souple initiale des prereleases
Prévision de départ, à recalibrer en `pre.001` :
### pre.001 — audit / route model / sizing
Audit complet Config/Transport/Store/Worker/Desk, matrice route-capability, gaps, UX et plan.
### pre.002 — contrats applicatifs de route et scaffold Desk
Créer la crate/app Tauri sur le gabarit KSP, DTO sûrs, route IDs/states et composite minimal sans Start Worker.
### pre.003 — Config -> route inventory
Résoudre réseau/profils/capabilities et produire les routes composables/non composables avec raisons sûres ; aucune sonde réseau arbitraire.
### pre.004 — composition RuntimeResources
Mapper chaque route réellement supportée vers ses ressources Worker/Transport sans bypass et revalider au Start.
### pre.005 — Store + Start/Stop mono-route
Ouvrir Store via façade, lancer/arrêter un Worker réel pour une route et fermer proprement les ressources.
### pre.006 — multi-route / un Worker par route
Gérer plusieurs routes sélectionnées, handles indépendants et Store partagé sans cross-Worker gap coordination.
### pre.007 — snapshots / monitoring
Projeter lifecycle, health, activity, backpressure, reconnect/replay, gap/repair et compteurs sûrs.
### pre.008 — frontend fonctionnel / tracing
Finaliser choix réseau/routes, Start/Stop, états, feedback, tracing frontend et ergonomie KSP.
### pre.009 — races/security/hardening
Config change entre inventory et Start, double Start/Stop, fermeture app, fault isolé, secrets/IPC/redaction et bornes.
### pre.010 — completeness / validation end-to-end
Canaris cross-layer Desk -> Config/Transport/Worker/Store, routes V1 complètes et scénarios système sans fake provider success.
### pre.011 — gate technique/live/Tauri
Workspace complet, Clippy strict, tests all-targets/all-features, npm/tsc/vite/Tauri build et smokes live réellement accessibles.
### pre.012 — réconciliation documentaire
README/USAGE, plan, validation, architectures réellement affectées. Pas de CHANGELOG/ROADMAP/prompt suivant.
### pre.013 — préparation publication
Prompt `0.3.16`, CHANGELOG, ROADMAP et mécanique de version/delta uniquement.
### rel.001
Publication stable mécanique après gate validé.
Cette numérotation est une prévision, pas une contrainte. Insérer/séparer des tranches si `pre.001` montre qu'un élément dépasse le budget ou appartient à une lower-layer.
## 13. Versionnement, deltas, commits, archives et tags
Appliquer `docs/rules/VERSION_WORKFLOW.md`.
Rappels :
```text
prerelease Cargo : 0.3.15-pre.N
livraison : 0.3.15-pre.NNN
fix livraison : 0.3.15-pre.NNN-fix.NNN
fix Cargo code/runtime : 0.3.15-pre.N.fix.N
archive générale : ksp-general-<delivery-id>.zip
archive doc-only : ksp-doc-<delivery-id>.zip seulement si réellement limitée à docs/prompts
```
À partir de `0.1.x`, chaque delta est commité. Aucun tag n'est requis pour prerelease/fix/rel. Après `rel.001` validée :
```text
commit : v0.3.15-rel.001
tag : v0.3.15
```
Le delta ZIP contient uniquement les fichiers ajoutés/modifiés de la livraison plus son delta ; aucun lockfile/cache/build output.
## 14. Procédure d'application et validation opérateur
Pour chaque delta :
```text
partir exactement de la base requise
appliquer le ZIP à la racine du dépôt
vérifier le diff avant exécution
ne jamais recopier un worktree complet à la place du delta
ne jamais modifier silencieusement un delta déjà livré
```
Gate Rust minimal après modification :
```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
```
Les tests ciblés suivent selon les crates modifiées.
## 15. Validations frontend/Tauri/réseau attendues
### 15.1 Rust/workspace
Avant fermeture technique :
```bash
cargo test --workspace --all-targets --all-features
cargo tree -p ksp-app-raw-transaction-ingest-desk --edges normal
cargo tree -p ksp-app-raw-transaction-ingest-desk -e features
cargo tree -p ksp-worker-raw-transaction-ingest-lib --edges normal
cargo tree --duplicates
```
### 15.2 Frontend
Depuis la crate Desk, exécuter réellement les commandes correspondant au package courant, typiquement :
```bash
npm run build
```
ou les commandes workspace réellement retenues après audit.
Le build frontend doit démontrer `tsc`/Vite sans réseau direct frontend, storage secret ou imports hors gabarit.
### 15.3 Tauri
Le gate final inclut :
```bash
cargo tauri build
```
pour la nouvelle Desk, avec bundles réellement supportés par l'environnement opérateur.
### 15.4 Live/system
Prévoir des scénarios réellement accessibles, par exemple :
```text
route composable -> Start -> Running -> RawTransaction/observations progressent -> Stop propre
route Config incomplète -> non sélectionnable sans I/O
route composable mais endpoint runtime indisponible -> Start fault propre
plusieurs routes -> plusieurs Workers -> même Store -> observations/idempotence cohérentes
```
Les profils/provider-gated ne sont PASS que si les credentials et capacités correspondantes sont réellement disponibles et le test a été exécuté.
Un exemple PublicNode Mainnet ou Helius Devnet n'est jamais considéré prouvé uniquement parce qu'un profil/exemple Config existe.
## 16. Critères de clôture de `0.3.15`
`0.3.15` est clôturable seulement si :
```text
ksp-app-raw-transaction-ingest-desk existe et build
route/source/endpoint sont distingués dans le modèle
catalogue V1 borné et documenté
toute route sélectionnable possède toutes ses capabilities Config obligatoires
aucune capability n'est déduite du seul provider
composabilité et opérabilité runtime sont distinguées
Config est revalidée au Start
1 route active = 1 Worker
aucun Worker par source physique imposé par la Desk
une route peut assembler plusieurs ressources Transport nécessaires
plusieurs Workers peuvent partager le même Store sans coordination de gaps cross-Worker
Start/Stop réel utilise ksp-worker-raw-transaction-ingest-lib
Transport/Config/Store ownership restent respectés
aucun endpoint/secret/Store URI ne traverse l'IPC
snapshots Worker utiles sont visibles sans source key privée
fault d'une route n'impose pas arbitrairement le fault des autres
fermeture app draine/arrête proprement tous les Workers détenus
aucun Backfill automatique
aucune persistence/RAW canonicalization réimplémentée dans la Desk
workspace/Clippy/tests/frontend/Tauri gates verts
smokes accessibles exécutés ou explicitement NON EXÉCUTÉ
README/USAGE/plan/validation réconciliés
prompt 0.3.16 produit dans la dernière prerelease
```
## 17. Release/session suivante envisagée
La trajectoire actuelle prévoit :
```text
0.3.16
étendre ksp-job-backfill-lib et ksp-app-backfill-desk vers les stratégies historiques/catch-up multi-source réellement pertinentes
```
`0.3.15` ne doit pas préimplémenter ce backfill. Le Desk ingest reste un gestionnaire de routes live continues.
## 18. Instruction d'ouverture
La prochaine session doit commencer par :
```text
1. vérifier la base stable v0.3.14 et workspace.package.version = 0.3.14
2. lire les règles normatives dans l'ordre demandé
3. lire le handoff 0.3.14 et le Worker final
4. auditer Config HTTP/WS/gRPC + Store réellement committed
5. auditer les constructeurs RuntimeResources Worker et le pattern Backfill Desk
6. construire la matrice route -> capabilities -> Config evidence -> Worker resources
7. vérifier les Desk KSP servant de gabarit
8. brainstormer les gaps/frontières/risques
9. recalibrer la prévision des prereleases
10. produire plan 036 + validation 032 + delta pre.001
```
Ne pas commencer avant ce gate :
```text
scaffold UI lourd
Start/Stop Worker
nouvelle API Config/Transport/Worker
nouveau provider
nouvelle logique de retry/hydration/repair
```
sauf correction triviale indispensable pour rendre l'audit lui-même exécutable.