v0.3.14-pre.015

This commit is contained in:
2026-09-12 23:07:58 +02:00
parent 8d8b6b9bcb
commit cd7cf2c4c6
11 changed files with 406 additions and 52 deletions

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-worker-raw-transaction-ingest-lib/README.md -->
<!-- version: 11 -->
<!-- version: 12 -->
# 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 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.
@@ -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`.
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
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.
## 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
source_total
@@ -228,21 +260,11 @@ source_replay_attempt_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
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.
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.
## Persistence Store