Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 002d08ba5f | |||
| a7ac30f39b | |||
| cd7cf2c4c6 | |||
| 8d8b6b9bcb | |||
| 02012f105a | |||
| 09c91172ae | |||
| 8a0717c839 | |||
| f3c4169752 | |||
| eedad4b81e | |||
| 4e39ddd5d0 | |||
| c99cb048bf | |||
| 8c4b458724 | |||
| 792560191c | |||
| c884ce88ba | |||
| 4bcd942928 | |||
| bf394e0e4b | |||
| ec94001ab8 | |||
| 505cc8c3f6 | |||
| 9fc30d3ab9 | |||
| 887664f3c0 | |||
| ae712eae64 | |||
| a3df8f6ef5 | |||
| 1006079654 | |||
| 6721a4142a | |||
| 7bf938c19f | |||
| 5932250314 | |||
| b2068e9ef2 | |||
| 2ca56dbaf4 | |||
| 9785cda39d | |||
| 0cea99cd5e | |||
| 71bc754feb | |||
| cabbfed730 | |||
| 8b6ef7778a | |||
| 06319655b0 | |||
| f636169783 | |||
| 0f8f38ff52 | |||
| 142998afb3 | |||
| 665d7d2bb6 | |||
| 1746d47412 | |||
| 9bb2f7bcae | |||
| 1f2720463b | |||
| 8bb530e9b4 | |||
| 72df965a9c | |||
| f688325170 | |||
| c711213dcc | |||
| 11ecdccd65 | |||
| 27ab84d198 | |||
| 28e4da3879 | |||
| 31c0a83e51 | |||
| 34fddc3bf5 | |||
| 13c3dac554 | |||
| f7ed053e8e | |||
| 2e8feb56dd | |||
| 793178b345 | |||
| ee0359efd5 |
26
CHANGELOG.md
26
CHANGELOG.md
@@ -1,8 +1,32 @@
|
|||||||
<!-- file: CHANGELOG.md -->
|
<!-- file: CHANGELOG.md -->
|
||||||
<!-- version: 32 -->
|
<!-- 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` 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.
|
||||||
|
|
||||||
|
La convergence partage un registre global d'hydration entre Yellowstone, Standard Logs et Helius afin de coalescer `(network, signature, commitment)` avant fan-out HTTP. Après canonicalisation, les acquisitions de même `(network, signature)` sont sérialisées de manière bornée : un contenu identique conserve les observations/provenances distinctes, tandis qu'une divergence devient un `content_conflict` terminal explicite. Aucune majorité provider, préférence silencieuse ou stratégie `first-provider-wins` n'est introduite. Standard Block et HTTP Block Polling admettent directement les transactions Legacy/V0/V1 qualifiées avec `maxSupportedTransactionVersion = 1`.
|
||||||
|
|
||||||
|
Le supervisor multi-source possède toutes les tâches et applique des bornes source-neutral sur admission, pending signals, hydrations et persistence avec une fairness minimale entre sources reference-bearing. Les snapshots agrègent activity/health/backpressure/reconnect/replay/gap sans exposer le matériau provider. La politique reste conservative : une source configurée qui échoue est terminale faute d'équivalence de coverage prouvée ; le gap repair, les rôles degraded/failover et la preuve de couverture restent donc `0.3.14`. Le shutdown stop/fault/drain/abort+join est durci contre les races, leaders d'hydration abandonnés, Store lent et publications tardives.
|
||||||
|
|
||||||
|
Le gate déterministe final passe `cargo 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 850` 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 `transactionSubscribe` live, `blockSubscribe` provider dédié, HTTP polling Worker end-to-end et Worker multi-source vers Store restent explicitement non exécutés faute de gate provisionné ; aucune fixture ne les requalifie en PASS.
|
||||||
|
|
||||||
|
`prompts/033-V0_3_14_START_PROMPT.md` ouvre `0.3.14` exclusivement depuis le futur tag stable `v0.3.13`. Cette release doit fermer le gap repair/hardening multi-source limité au run actif — replay adressable, coverage redondante, scan HTTP borné, hydration de réparation, unresolved gaps et health policy — sans transformer le Worker en moteur historique ni créer de dépendance vers `ksp-job-backfill-lib`.
|
||||||
|
|
||||||
## 0.3.12 — Yellowstone + hydration HTTP + continuité de run du Worker RawTransaction — 2026-09-09
|
## 0.3.12 — Yellowstone + hydration HTTP + continuité de run du Worker RawTransaction — 2026-09-09
|
||||||
|
|
||||||
`0.3.12` ouvre la première source réseau productive de `ksp-worker-raw-transaction-ingest-lib` sans remettre en cause la fondation source-neutral de `0.3.11`. Le Worker dépend désormais de la façade `ksp-onchain-transport-lib` mais reste sans Config, Job Backfill, backend Store physique, `reqwest`, `tonic` ou proto provider direct. `RawTransactionIngestRuntimeResources` reçoit une source Yellowstone caller-composed et un pool HTTP cohérent ; `start` conserve la fondation sans source, tandis que `start_with_runtime_resources` lance la verticale live supervisée.
|
`0.3.12` ouvre la première source réseau productive de `ksp-worker-raw-transaction-ingest-lib` sans remettre en cause la fondation source-neutral de `0.3.11`. Le Worker dépend désormais de la façade `ksp-onchain-transport-lib` mais reste sans Config, Job Backfill, backend Store physique, `reqwest`, `tonic` ou proto provider direct. `RawTransactionIngestRuntimeResources` reçoit une source Yellowstone caller-composed et un pool HTTP cohérent ; `start` conserve la fondation sans source, tandis que `start_with_runtime_resources` lance la verticale live supervisée.
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
# file: Cargo.toml
|
# file: Cargo.toml
|
||||||
# version: 539
|
# 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.12"
|
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"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: README.md -->
|
<!-- file: README.md -->
|
||||||
<!-- version: 13 -->
|
<!-- version: 14 -->
|
||||||
|
|
||||||
# Khadhroony Solana Project
|
# Khadhroony Solana Project
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ La couche RAW dispose d'une façade Store backend-neutral et d'un premier job hi
|
|||||||
|
|
||||||
`ksp-worker-api` fournit désormais la fondation générique des services continus : identité Worker, lifecycle borné, health/activity, intention de stop coopératif et observation latest-value. Cette API reste Core-only, runtime-neutral et sans connaissance Solana, Transport, Store ou Job. Elle ne démarre ni n'arrête elle-même un runtime concret.
|
`ksp-worker-api` fournit désormais la fondation générique des services continus : identité Worker, lifecycle borné, health/activity, intention de stop coopératif et observation latest-value. Cette API reste Core-only, runtime-neutral et sans connaissance Solana, Transport, Store ou Job. Elle ne démarre ni n'arrête elle-même un runtime concret.
|
||||||
|
|
||||||
`ksp-worker-raw-transaction-ingest-lib` est le premier Worker concret distinct du Job Backfill. Il conserve sa fondation Start/Stop, admission bornée, Common RAW, Store backend-neutral, shutdown borné et snapshots latest-value, et possède maintenant une première source productive Yellowstone + hydration HTTP `getTransaction`. Les signaux `Transaction`, `TransactionStatus` et transactions de `Block` sont coalescés puis hydratés avant admission RAW ; `BlockMeta` et `Slot` restent des signaux de continuité. Le reconnect/replay reste propriétaire de Transport, la frontier de traitement reste run-local et un gap de rétention prouvé provoque un fault au lieu de lancer une campagne historique. `ksp-job-backfill-lib` conserve son rôle historique paramétré et borné ; les deux producteurs restent indépendants et convergent uniquement vers les mêmes contrats RAW/Store.
|
`ksp-worker-raw-transaction-ingest-lib` est le premier Worker concret distinct du Job Backfill. Il conserve sa fondation Start/Stop, admission bornée, Common RAW, Store backend-neutral, shutdown borné et snapshots latest-value. Ses sources productives couvrent maintenant Yellowstone + hydration HTTP `getTransaction`, Standard WS `logsSubscribe` + hydration, Standard WS `blockSubscribe` RAW-direct, Helius `transactionSubscribe` + hydration et Solana HTTP live block polling. Le polling HTTP démarre au `getSlot` observé du run, découvre uniquement les blocs live via `getBlocksWithLimit` puis matérialise les slots listés via `getBlock observed`; il n'est jamais utilisé comme Backfill implicite. Les signaux hydratés de même `(network, signature, commitment)` convergent vers le même coordinator, tandis que les voies block RAW-direct passent par la même admission Common RAW. Le reconnect/replay reste propriétaire de Transport, la frontier de traitement reste run-local et un gap de rétention prouvé provoque un fault au lieu de lancer une campagne historique. `ksp-job-backfill-lib` conserve son rôle historique paramétré et borné ; les deux producteurs restent indépendants et convergent uniquement vers les mêmes contrats RAW/Store.
|
||||||
|
|
||||||
## Points d'entrée
|
## Points d'entrée
|
||||||
|
|
||||||
|
|||||||
10
ROADMAP.md
10
ROADMAP.md
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: ROADMAP.md -->
|
<!-- file: ROADMAP.md -->
|
||||||
<!-- version: 110 -->
|
<!-- version: 112 -->
|
||||||
|
|
||||||
# Roadmap KSP
|
# Roadmap KSP
|
||||||
|
|
||||||
@@ -105,9 +105,9 @@ RAW -> STRUCTURAL -> DECODED -> DOMAIN
|
|||||||
- [X] `0.3.10` — Lower-layer RAW Transaction commune stabilisée : `ksp-raw-transaction-lib` possède la canonicalisation RAW v1 source-neutral partagée, le Backfill est migré sans changement des golden bytes/hash, Transport expose `get_block_observed`, le wire Solana Legacy/V0/V1 est matérialisé et les canaris HTTP/WS standard/Helius/Yellowstone qualifient précisément les voies directes ou nécessitant hydration. La release se ferme sans runtime Worker concret ; celui-ci commence en `0.3.11` selon le redécoupage `0.3.11`–`0.3.14`.
|
- [X] `0.3.10` — Lower-layer RAW Transaction commune stabilisée : `ksp-raw-transaction-lib` possède la canonicalisation RAW v1 source-neutral partagée, le Backfill est migré sans changement des golden bytes/hash, Transport expose `get_block_observed`, le wire Solana Legacy/V0/V1 est matérialisé et les canaris HTTP/WS standard/Helius/Yellowstone qualifient précisément les voies directes ou nécessitant hydration. La release se ferme sans runtime Worker concret ; celui-ci commence en `0.3.11` selon le redécoupage `0.3.11`–`0.3.14`.
|
||||||
- [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.
|
||||||
- [ ] `0.3.13` — Ajouter les voies **WS standard / Helius / HTTP live** et leur convergence multi-source : `logsSubscribe + getTransaction`, `blockSubscribe`, `transactionSubscribe + hydration`, polling blocs HTTP, observations multiples, content conflict explicite, coalescence et backpressure sans second actor Transport.
|
- [X] `0.3.13` — Convergence live multi-source du Worker RAW livrée : composition caller-owned `1..32` sources d’un 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 n’est 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 n’est sélectionnable que si toutes ses requirements sont composables depuis Config sur le réseau choisi ; la présence d’un 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 l’opé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
|
||||||
@@ -119,7 +119,7 @@ RAW -> STRUCTURAL -> DECODED -> DOMAIN
|
|||||||
|
|
||||||
- [ ] **TODO** — maintenir la matrice d’admission HTTP/WS/gRPC/provider lors de toute nouvelle famille D1 : plusieurs sources ne convergent vers un même struct que si elles satisfont la même sémantique sans perte.
|
- [ ] **TODO** — maintenir la matrice d’admission HTTP/WS/gRPC/provider lors de toute nouvelle famille D1 : plusieurs sources ne convergent vers un même struct que si elles satisfont la même sémantique sans perte.
|
||||||
- [X] **TODO `0.3.9`** — matrice RAW Transaction unique produite et consolidée dans `docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md`, avec séparation possibilités/support/preuve, capabilities orthogonales aux producteurs, provenance/déduplication/continuité et handoffs désormais scindés `0.3.11`–`0.3.14` Worker live / `0.3.16` Backfill historique.
|
- [X] **TODO `0.3.9`** — matrice RAW Transaction unique produite et consolidée dans `docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md`, avec séparation possibilités/support/preuve, capabilities orthogonales aux producteurs, provenance/déduplication/continuité et handoffs désormais scindés `0.3.11`–`0.3.14` Worker live / `0.3.16` Backfill historique.
|
||||||
- [ ] **TODO Helius `0.3.13`** — ajouter uniquement les profils/capabilities Helius réellement nécessaires au Worker live après revalidation des surfaces/tier courants ; réutiliser exclusivement `KSP_SECRET_HELIUS_API_KEY` via Config et la surface `transactionSubscribe` déjà Transport-owned, sans SDK provider ni second client parallèle.
|
- [X] **TODO Helius `0.3.13`** — la voie Worker Helius `transactionSubscribe` réutilise la surface Transport et les profils Config Helius déjà existants avec `KSP_SECRET_HELIUS_API_KEY`; aucun nouveau profil, secret, SDK provider, tier codé ou second client parallèle n’a été nécessaire. Le payload riche reste Transport-owned et la voie Worker hydrate via `getTransaction observed` tant que le RAW complet n’est pas prouvé directement.
|
||||||
- [X] **TODO réseau** — identité KSP canonique fixée à `mainnet` dans Config/Store/Transport/tests ; `mainnet-beta` reste uniquement un alias legacy/externe lorsque la frontière provider l'exige. Les données D1 RAW antérieures restent non autoritaires pendant cette phase et aucune compatibilité de base de test n'impose l'ancien libellé.
|
- [X] **TODO réseau** — identité KSP canonique fixée à `mainnet` dans Config/Store/Transport/tests ; `mainnet-beta` reste uniquement un alias legacy/externe lorsque la frontière provider l'exige. Les données D1 RAW antérieures restent non autoritaires pendant cette phase et aucune compatibilité de base de test n'impose l'ancien libellé.
|
||||||
- [X] `RawAccountState` + observation — contrat commun stabilisé en `0.3.1` avec bytes complets + slot, provenance séparée et enrichissements source-specific optionnels ; la persistence PostgreSQL physique est complétée en `0.3.4` avec les quatre capabilities account et la conformance RAW 10/10.
|
- [X] `RawAccountState` + observation — contrat commun stabilisé en `0.3.1` avec bytes complets + slot, provenance séparée et enrichissements source-specific optionnels ; la persistence PostgreSQL physique est complétée en `0.3.4` avec les quatre capabilities account et la conformance RAW 10/10.
|
||||||
- [ ] **TODO** — statut/commitment transactionnel restant : `0.3.5` couvre uniquement le fait passif d’exécution `slot + signature + outcome`; réauditer séparément `signatureSubscribe` et `getSignatureStatuses` lorsqu’un consumer de commitment/snapshot réel apparaît, sans fusionner snapshot, transition et execution update dans un modèle Option-soup.
|
- [ ] **TODO** — statut/commitment transactionnel restant : `0.3.5` couvre uniquement le fait passif d’exécution `slot + signature + outcome`; réauditer séparément `signatureSubscribe` et `getSignatureStatuses` lorsqu’un consumer de commitment/snapshot réel apparaît, sans fusionner snapshot, transition et execution update dans un modèle Option-soup.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-onchain-transport-lib/src/grpc_subscribe.rs
|
// file: crates/ksp-onchain-transport-lib/src/grpc_subscribe.rs
|
||||||
// version: 8
|
// version: 9
|
||||||
|
|
||||||
const MAX_GRPC_BLOCKHASH_TEXT_LENGTH_BYTES: usize = 128;
|
const MAX_GRPC_BLOCKHASH_TEXT_LENGTH_BYTES: usize = 128;
|
||||||
const MAX_GRPC_BLOCK_VECTOR_COUNT: usize = 65_536;
|
const MAX_GRPC_BLOCK_VECTOR_COUNT: usize = 65_536;
|
||||||
@@ -2529,13 +2529,34 @@ impl YellowstoneSubscribeEntryFilter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Opaque deterministic identity material for one complete Yellowstone Subscribe request.
|
||||||
|
///
|
||||||
|
/// The encoded bytes remain private. `Hash` feeds those canonical bytes to a caller-provided hasher while `Debug` exposes only their length.
|
||||||
|
#[derive(Clone, Eq, PartialEq)]
|
||||||
|
pub struct YellowstoneSubscribeRequestIdentity {
|
||||||
|
bytes: std::vec::Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::hash::Hash for crate::YellowstoneSubscribeRequestIdentity {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
state.write(&(self.bytes.len() as u64).to_be_bytes());
|
||||||
|
state.write(self.bytes.as_slice());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for crate::YellowstoneSubscribeRequestIdentity {
|
||||||
|
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
return formatter.debug_struct("YellowstoneSubscribeRequestIdentity").field("byte_len", &self.bytes.len()).finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Provider-neutral standard Yellowstone subscribe request owned by KSP.
|
/// Provider-neutral standard Yellowstone subscribe request owned by KSP.
|
||||||
///
|
///
|
||||||
/// The seven upstream maps are represented independently and retain named empty entries. An entirely empty map is the logical KSP representation of no active
|
/// The seven upstream maps are represented independently and retain named empty entries. An entirely empty map is the logical KSP representation of no active
|
||||||
/// filter in that family; protobuf map encoding does not distinguish an omitted map from an empty map. Filter-group names are globally unique across all seven
|
/// filter in that family; protobuf map encoding does not distinguish an omitted map from an empty map. Filter-group names are globally unique across all seven
|
||||||
/// maps so the names echoed by `SubscribeUpdate.filters` remain unambiguous. `Debug` exposes only counts and common scalar options, never filter names or
|
/// maps so the names echoed by `SubscribeUpdate.filters` remain unambiguous. `Debug` exposes only counts and common scalar options, never filter names or
|
||||||
/// future
|
/// future filter payloads.
|
||||||
/// filter payloads.
|
|
||||||
#[derive(Clone, Eq, PartialEq)]
|
#[derive(Clone, Eq, PartialEq)]
|
||||||
pub struct YellowstoneSubscribeRequest {
|
pub struct YellowstoneSubscribeRequest {
|
||||||
accounts: std::collections::BTreeMap<crate::YellowstoneSubscribeFilterName, crate::YellowstoneSubscribeAccountFilter>,
|
accounts: std::collections::BTreeMap<crate::YellowstoneSubscribeFilterName, crate::YellowstoneSubscribeAccountFilter>,
|
||||||
@@ -2551,7 +2572,7 @@ pub struct YellowstoneSubscribeRequest {
|
|||||||
from_slot: std::option::Option<u64>,
|
from_slot: std::option::Option<u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl YellowstoneSubscribeRequest {
|
impl crate::YellowstoneSubscribeRequest {
|
||||||
/// Creates an empty subscribe request. Empty requests are valid because later bidi lifecycle code uses request mutations to clear filters or carry ping
|
/// Creates an empty subscribe request. Empty requests are valid because later bidi lifecycle code uses request mutations to clear filters or carry ping
|
||||||
/// state.
|
/// state.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@@ -2762,6 +2783,40 @@ impl YellowstoneSubscribeRequest {
|
|||||||
return self.from_slot;
|
return self.from_slot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Builds one opaque deterministic identity for the complete logical Subscribe request.
|
||||||
|
///
|
||||||
|
/// The identity preserves filter-family separation, globally sorted filter names, exact filter wire payloads and common request options. Its Debug surface
|
||||||
|
/// exposes only the encoded byte length. Callers may hash the opaque value but cannot recover the underlying identity bytes through this API.
|
||||||
|
pub fn identity(&self) -> ksp_core_lib::Result<crate::YellowstoneSubscribeRequestIdentity> {
|
||||||
|
let validation = self.validate();
|
||||||
|
if let std::result::Result::Err(error) = validation {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
let mut bytes = std::vec::Vec::new();
|
||||||
|
append_subscribe_identity_map(&mut bytes, b"accounts", &self.accounts, |filter| return filter.to_wire());
|
||||||
|
append_subscribe_identity_map(&mut bytes, b"blocks", &self.blocks, |filter| return filter.to_wire());
|
||||||
|
append_subscribe_identity_map(&mut bytes, b"blocks_meta", &self.blocks_meta, |filter| return filter.to_wire());
|
||||||
|
append_subscribe_identity_map(&mut bytes, b"entry", &self.entry, |filter| return filter.to_wire());
|
||||||
|
append_subscribe_identity_map(&mut bytes, b"slots", &self.slots, |filter| return filter.to_wire());
|
||||||
|
append_subscribe_identity_map(&mut bytes, b"transactions", &self.transactions, |filter| return filter.to_wire());
|
||||||
|
append_subscribe_identity_map(&mut bytes, b"transactions_status", &self.transactions_status, |filter| return filter.to_wire());
|
||||||
|
let mut common = match self.to_wire() {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
common.accounts.clear();
|
||||||
|
common.blocks.clear();
|
||||||
|
common.blocks_meta.clear();
|
||||||
|
common.entry.clear();
|
||||||
|
common.slots.clear();
|
||||||
|
common.transactions.clear();
|
||||||
|
common.transactions_status.clear();
|
||||||
|
let common = yellowstone_grpc_proto::prost::Message::encode_to_vec(&common);
|
||||||
|
append_subscribe_identity_component(&mut bytes, b"common");
|
||||||
|
append_subscribe_identity_component(&mut bytes, common.as_slice());
|
||||||
|
return std::result::Result::Ok(crate::YellowstoneSubscribeRequestIdentity { bytes });
|
||||||
|
}
|
||||||
|
|
||||||
/// Validates all deterministic common subscribe-request bounds before any network I/O.
|
/// Validates all deterministic common subscribe-request bounds before any network I/O.
|
||||||
pub fn validate(&self) -> ksp_core_lib::Result<()> {
|
pub fn validate(&self) -> ksp_core_lib::Result<()> {
|
||||||
if self.total_filter_count() > MAX_GRPC_SUBSCRIBE_FILTER_GROUP_COUNT {
|
if self.total_filter_count() > MAX_GRPC_SUBSCRIBE_FILTER_GROUP_COUNT {
|
||||||
@@ -3710,13 +3765,38 @@ fn commitment_to_wire(commitment: std::option::Option<crate::SolanaCommitment>)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::default::Default for YellowstoneSubscribeRequest {
|
fn append_subscribe_identity_component(output: &mut std::vec::Vec<u8>, value: &[u8]) {
|
||||||
|
output.extend_from_slice(&(value.len() as u64).to_be_bytes());
|
||||||
|
output.extend_from_slice(value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn append_subscribe_identity_map<V, W, F>(
|
||||||
|
output: &mut std::vec::Vec<u8>,
|
||||||
|
family: &[u8],
|
||||||
|
values: &std::collections::BTreeMap<crate::YellowstoneSubscribeFilterName, V>,
|
||||||
|
mut to_wire: F,
|
||||||
|
) where
|
||||||
|
W: yellowstone_grpc_proto::prost::Message,
|
||||||
|
F: FnMut(&V) -> W,
|
||||||
|
{
|
||||||
|
append_subscribe_identity_component(output, family);
|
||||||
|
output.extend_from_slice(&(values.len() as u64).to_be_bytes());
|
||||||
|
for (name, filter) in values {
|
||||||
|
append_subscribe_identity_component(output, name.as_str().as_bytes());
|
||||||
|
let wire = yellowstone_grpc_proto::prost::Message::encode_to_vec(&to_wire(filter));
|
||||||
|
append_subscribe_identity_component(output, wire.as_slice());
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::default::Default for crate::YellowstoneSubscribeRequest {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
return Self::new();
|
return Self::new();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for YellowstoneSubscribeRequest {
|
impl std::fmt::Debug for crate::YellowstoneSubscribeRequest {
|
||||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
return formatter
|
return formatter
|
||||||
.debug_struct("YellowstoneSubscribeRequest")
|
.debug_struct("YellowstoneSubscribeRequest")
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-onchain-transport-lib/src/lib.rs
|
// file: crates/ksp-onchain-transport-lib/src/lib.rs
|
||||||
// version: 46
|
// version: 48
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![deny(unreachable_pub)]
|
#![deny(unreachable_pub)]
|
||||||
@@ -215,6 +215,8 @@ pub use self::grpc_subscribe::YellowstoneSubscribePingUpdate;
|
|||||||
pub use self::grpc_subscribe::YellowstoneSubscribePongUpdate;
|
pub use self::grpc_subscribe::YellowstoneSubscribePongUpdate;
|
||||||
/// Provider-neutral standard Yellowstone Subscribe request.
|
/// Provider-neutral standard Yellowstone Subscribe request.
|
||||||
pub use self::grpc_subscribe::YellowstoneSubscribeRequest;
|
pub use self::grpc_subscribe::YellowstoneSubscribeRequest;
|
||||||
|
/// Opaque deterministic identity for one complete Yellowstone Subscribe request.
|
||||||
|
pub use self::grpc_subscribe::YellowstoneSubscribeRequestIdentity;
|
||||||
/// Complete slot-family filter group for standard Yellowstone Subscribe.
|
/// Complete slot-family filter group for standard Yellowstone Subscribe.
|
||||||
pub use self::grpc_subscribe::YellowstoneSubscribeSlotFilter;
|
pub use self::grpc_subscribe::YellowstoneSubscribeSlotFilter;
|
||||||
/// Complete transaction-family filter shared by transactions and transaction-status maps.
|
/// Complete transaction-family filter shared by transactions and transaction-status maps.
|
||||||
@@ -553,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.
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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: 51
|
// 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;
|
||||||
@@ -1032,3 +1045,15 @@ fn public_v0_3_10_pre_004_observed_get_block_surface_is_available_from_crate_roo
|
|||||||
let _ = method;
|
let _ = method;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn public_v0_3_13_pre_002_yellowstone_subscribe_identity_is_opaque_and_available_from_crate_root() {
|
||||||
|
let request = ksp_onchain_transport_lib::YellowstoneSubscribeRequest::new();
|
||||||
|
let identity = request.identity().expect("empty validated request identity must build");
|
||||||
|
let _identity_type = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneSubscribeRequestIdentity>();
|
||||||
|
let debug = std::format!("{identity:?}");
|
||||||
|
assert!(debug.contains("YellowstoneSubscribeRequestIdentity"));
|
||||||
|
assert!(debug.contains("byte_len"));
|
||||||
|
assert!(!debug.contains("bytes:"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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: 43
|
// version: 46
|
||||||
|
|
||||||
//! Release-level completeness canaries for staged HTTP and WebSocket Transport coverage.
|
//! Release-level completeness canaries for staged HTTP and WebSocket Transport coverage.
|
||||||
|
|
||||||
@@ -1339,3 +1339,63 @@ fn release_v0_2_9_pre_010_adds_bounded_reconnect_replay_and_conservative_continu
|
|||||||
let _snapshot = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot>();
|
let _snapshot = std::any::type_name::<ksp_onchain_transport_lib::YellowstoneGrpcSubscribeSnapshot>();
|
||||||
let _state = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeState::Reconnecting;
|
let _state = ksp_onchain_transport_lib::YellowstoneGrpcSubscribeState::Reconnecting;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn release_v0_3_13_pre_002_yellowstone_subscribe_identity_remains_opaque_and_dependency_neutral() {
|
||||||
|
let source = include_str!("../src/grpc_subscribe.rs");
|
||||||
|
let root = include_str!("../src/lib.rs");
|
||||||
|
let identity_start = source.find("pub struct YellowstoneSubscribeRequestIdentity").expect("identity struct must remain present");
|
||||||
|
let request_start = source.find("/// Provider-neutral standard Yellowstone subscribe request").expect("request contract marker must remain present");
|
||||||
|
let identity_surface = &source[identity_start..request_start];
|
||||||
|
assert!(source.contains("pub fn identity(&self)"));
|
||||||
|
assert!(root.contains("pub use self::grpc_subscribe::YellowstoneSubscribeRequestIdentity;"));
|
||||||
|
assert!(!identity_surface.contains("pub fn bytes("));
|
||||||
|
assert!(!identity_surface.contains("pub fn as_bytes("));
|
||||||
|
assert!(!root.contains("pub use yellowstone_grpc_proto"));
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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"));
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-onchain-transport-lib/unit_tests/grpc_subscribe.rs
|
// file: crates/ksp-onchain-transport-lib/unit_tests/grpc_subscribe.rs
|
||||||
// version: 5
|
// version: 6
|
||||||
|
|
||||||
fn filter_name(value: &str) -> crate::YellowstoneSubscribeFilterName {
|
fn filter_name(value: &str) -> crate::YellowstoneSubscribeFilterName {
|
||||||
return crate::YellowstoneSubscribeFilterName::new(value).expect("fixture filter name must validate");
|
return crate::YellowstoneSubscribeFilterName::new(value).expect("fixture filter name must validate");
|
||||||
@@ -138,6 +138,37 @@ fn yellowstone_subscribe_common_bounds_reject_before_wire_conversion() {
|
|||||||
assert!(filters.insert_account_filter(excess_name, crate::YellowstoneSubscribeAccountFilter::new()).is_err());
|
assert!(filters.insert_account_filter(excess_name, crate::YellowstoneSubscribeAccountFilter::new()).is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn yellowstone_subscribe_request_identity_is_order_stable_exact_and_debug_redacted() {
|
||||||
|
let mut first = crate::YellowstoneSubscribeRequest::new();
|
||||||
|
let mut second = crate::YellowstoneSubscribeRequest::new();
|
||||||
|
let mut changed = crate::YellowstoneSubscribeRequest::new();
|
||||||
|
let mut filter_a = crate::YellowstoneSubscribeTransactionFilter::new();
|
||||||
|
filter_a.set_failed(std::option::Option::Some(false));
|
||||||
|
let mut filter_b = crate::YellowstoneSubscribeTransactionFilter::new();
|
||||||
|
filter_b.set_vote(std::option::Option::Some(false));
|
||||||
|
assert!(first.insert_transaction_filter(filter_name("identity-filter-beta-canary"), filter_b.clone()).is_ok());
|
||||||
|
assert!(first.insert_transaction_filter(filter_name("identity-filter-alpha-canary"), filter_a.clone()).is_ok());
|
||||||
|
assert!(second.insert_transaction_filter(filter_name("identity-filter-alpha-canary"), filter_a.clone()).is_ok());
|
||||||
|
assert!(second.insert_transaction_filter(filter_name("identity-filter-beta-canary"), filter_b.clone()).is_ok());
|
||||||
|
filter_a.set_failed(std::option::Option::Some(true));
|
||||||
|
assert!(changed.insert_transaction_filter(filter_name("identity-filter-alpha-canary"), filter_a).is_ok());
|
||||||
|
assert!(changed.insert_transaction_filter(filter_name("identity-filter-beta-canary"), filter_b).is_ok());
|
||||||
|
first.set_commitment(std::option::Option::Some(crate::SolanaCommitment::Confirmed));
|
||||||
|
second.set_commitment(std::option::Option::Some(crate::SolanaCommitment::Confirmed));
|
||||||
|
changed.set_commitment(std::option::Option::Some(crate::SolanaCommitment::Confirmed));
|
||||||
|
let first_identity = first.identity().expect("first request identity must build");
|
||||||
|
let second_identity = second.identity().expect("second request identity must build");
|
||||||
|
let changed_identity = changed.identity().expect("changed request identity must build");
|
||||||
|
assert_eq!(first_identity, second_identity);
|
||||||
|
assert_ne!(first_identity, changed_identity);
|
||||||
|
let debug = std::format!("{first_identity:?}");
|
||||||
|
assert!(debug.contains("byte_len"));
|
||||||
|
assert!(!debug.contains("identity-filter-alpha-canary"));
|
||||||
|
assert!(!debug.contains("identity-filter-beta-canary"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn yellowstone_subscribe_debug_omits_filter_names_and_future_payloads() {
|
fn yellowstone_subscribe_debug_omits_filter_names_and_future_payloads() {
|
||||||
let mut request = crate::YellowstoneSubscribeRequest::new();
|
let mut request = crate::YellowstoneSubscribeRequest::new();
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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: 2 -->
|
<!-- version: 12 -->
|
||||||
|
|
||||||
# ksp-worker-raw-transaction-ingest-lib
|
# ksp-worker-raw-transaction-ingest-lib
|
||||||
|
|
||||||
@@ -12,22 +12,46 @@ RawTransactionIngestWorker::start
|
|||||||
-> fondation source-neutral, sans source productive
|
-> fondation source-neutral, sans source productive
|
||||||
|
|
||||||
RawTransactionIngestWorker::start_with_runtime_resources
|
RawTransactionIngestWorker::start_with_runtime_resources
|
||||||
-> même runtime + une source Yellowstone productive supervisée
|
-> même runtime + 1..32 sources productives supervisées simultanément
|
||||||
+ hydration HTTP getTransaction
|
Yellowstone, WS standard logsSubscribe, WS standard blockSubscribe, Helius transactionSubscribe et/ou HTTP block polling
|
||||||
|
+ hydration HTTP getTransaction lorsque la source produit une référence
|
||||||
```
|
```
|
||||||
|
|
||||||
Le Worker reste indépendant de Config et de tout backend Store physique. Le caller compose les ressources Transport et Store, puis les remet à la crate par ses façades publiques.
|
Le Worker reste indépendant de Config et de tout backend Store physique. Le caller compose les ressources Transport et Store, puis les remet à la crate par ses façades publiques.
|
||||||
|
|
||||||
## Pipeline productif actuel
|
## Pipeline productif actuel
|
||||||
|
|
||||||
La première verticale live est :
|
Les verticales live productives sont :
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Yellowstone standard subscribe
|
Yellowstone standard subscribe
|
||||||
-> Transaction / TransactionStatus / Block
|
-> Transaction / TransactionStatus / Block
|
||||||
-> signal source-neutral (network, signature, slot, commitment, provenance sûre)
|
-> signal source-neutral (network, signature, slot, commitment, provenance sûre)
|
||||||
|
|
||||||
|
Solana standard WS logsSubscribe
|
||||||
|
-> context.slot + signature
|
||||||
|
-> signal source-neutral (network, signature, slot, commitment, provenance sûre)
|
||||||
-> coalescence bornée par (network, signature, commitment)
|
-> coalescence bornée par (network, signature, commitment)
|
||||||
-> HTTP getTransaction observed
|
-> HTTP getTransaction observed, Base64, maxSupportedTransactionVersion=1
|
||||||
|
|
||||||
|
Solana standard WS blockSubscribe
|
||||||
|
-> Full + Base64 + maxSupportedTransactionVersion=1 + showRewards=false
|
||||||
|
-> qualification explicite Legacy / V0 / V1
|
||||||
|
-> matériau Common RAW direct par transaction du bloc
|
||||||
|
|
||||||
|
Helius transactionSubscribe
|
||||||
|
-> Full + Base64 + maxSupportedTransactionVersion=1 + showRewards=false
|
||||||
|
-> signature + slot + transactionIndex uniquement dans le signal Worker
|
||||||
|
-> coalescence bornée par (network, signature, commitment)
|
||||||
|
-> HTTP getTransaction observed avant Common RAW
|
||||||
|
|
||||||
|
Solana HTTP live block polling
|
||||||
|
-> getSlot borne le run courant
|
||||||
|
-> getBlocksWithLimit découvre les blocs live
|
||||||
|
-> getBlock observed matérialise Full/Base64 Legacy/V0/V1
|
||||||
|
-> matériau Common RAW direct par transaction du bloc
|
||||||
|
|
||||||
|
les chemins productifs
|
||||||
-> ksp-raw-transaction-lib
|
-> ksp-raw-transaction-lib
|
||||||
-> admission centrale bornée
|
-> admission centrale bornée
|
||||||
-> ksp-store-lib
|
-> ksp-store-lib
|
||||||
@@ -58,7 +82,77 @@ La construction est sans I/O et refuse notamment :
|
|||||||
- une provenance provider/endpoint non représentable ;
|
- une provenance provider/endpoint non représentable ;
|
||||||
- l'absence d'une route HTTP compatible pour `getTransaction` sur le même réseau.
|
- l'absence d'une route HTTP compatible pour `getTransaction` sur le même réseau.
|
||||||
|
|
||||||
Le runtime-resource aggregate public contient exactement une source Yellowstone validée. Il n'expose ni enum provider, ni collection de sources, ni callback, ni queue d'enqueue, ni client inférieur.
|
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 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.
|
||||||
|
|
||||||
|
## Contrat de source Standard Logs + HTTP
|
||||||
|
|
||||||
|
`RawTransactionIngestStandardLogsSource::new` reçoit :
|
||||||
|
|
||||||
|
```text
|
||||||
|
WsEndpointSettings kind solana_standard
|
||||||
|
SolanaLogsSubscribeFilter
|
||||||
|
SolanaCommitment Confirmed ou Finalized
|
||||||
|
HttpTransportPool
|
||||||
|
HttpRoleName d'hydration
|
||||||
|
```
|
||||||
|
|
||||||
|
La construction est sans I/O. Elle refuse un endpoint WS invalide ou non standard, `Processed`, un réseau/provenance non représentable et l'absence de route HTTP `getTransaction` compatible sur le même réseau. Le filtre `All`, `AllWithVotes` ou `Mentions(pubkey)` participe uniquement à une empreinte privée ; le pubkey d'un filtre `Mentions` n'est pas recopié dans `Debug`, snapshot ou provenance textuelle.
|
||||||
|
|
||||||
|
Au runtime, le Worker ouvre `SolanaStandardWsSession::connect`, puis `logs_subscribe`. Les lignes de logs et `err` restent dans Transport et ne sont jamais stockées dans le signal Worker. Seuls `context.slot` et `signature` sont projetés vers l'hydration commune. Reconnect, resubscribe et backpressure de la subscription restent possédés par Transport.
|
||||||
|
|
||||||
|
## Contrat de source Standard Block direct RAW
|
||||||
|
|
||||||
|
`RawTransactionIngestStandardBlockSource::new` reçoit :
|
||||||
|
|
||||||
|
```text
|
||||||
|
WsEndpointSettings kind solana_standard
|
||||||
|
SolanaBlockSubscribeFilter
|
||||||
|
SolanaCommitment Confirmed ou Finalized
|
||||||
|
```
|
||||||
|
|
||||||
|
Le runtime ouvre la `SolanaStandardWsSession` existante et demande exactement `Base64`, `Full`, `maxSupportedTransactionVersion = 1` et `showRewards = false`. Aucun `HttpTransportPool` n'est attaché à cette source : les transactions dont la version est explicitement `Legacy`, `0` ou `1` sont transformées directement en `RawTransactionMaterial` à partir du wire Base64 du bloc, avec signature embarquée, slot, block time, meta, version et index de transaction.
|
||||||
|
|
||||||
|
La qualification est fermée : une version omise/nulle ou supérieure à `1`, une transaction non Base64, `block: null`, une erreur distante de notification, un slot de contexte incohérent ou un champ transactions absent/nul termine la source par une erreur sûre. Aucun de ces cas n'est converti en bloc vide, en succès silencieux ou en progression artificielle de frontier.
|
||||||
|
|
||||||
|
Pour un bloc multi-transaction, le slot n'est projeté settled qu'après l'admission réussie de toutes ses transactions. Cette voie RAW-direct n'incrémente pas `hydration_pending`; un stop ou une erreur au milieu du bloc ne produit aucune progression artificielle. Reconnect, resubscribe et backpressure WebSocket restent possédés par Transport.
|
||||||
|
|
||||||
|
## Contrat de source Helius Transaction + HTTP
|
||||||
|
|
||||||
|
`RawTransactionIngestHeliusTransactionSource::new` reçoit :
|
||||||
|
|
||||||
|
```text
|
||||||
|
WsEndpointSettings kind helius_laserstream
|
||||||
|
HeliusTransactionSubscribeFilter
|
||||||
|
SolanaCommitment Confirmed ou Finalized
|
||||||
|
HttpTransportPool
|
||||||
|
HttpRoleName d'hydration
|
||||||
|
```
|
||||||
|
|
||||||
|
La construction est sans I/O. Elle réutilise le contrat Transport existant et impose `Full`, `Base64`, `showRewards = false` et `maxSupportedTransactionVersion = 1`. Le Worker ne lit pas Config, ne lit pas `KSP_SECRET_HELIUS_API_KEY` et ne code aucun tier provider ; l'URL résolue et le credential restent dans l'endpoint Transport fourni par le caller.
|
||||||
|
|
||||||
|
Au runtime, `HeliusLaserStreamWsSession::connect` puis `transaction_subscribe` sont utilisés. Seule une notification `Full` conforme au mode demandé est admise ; une forme `Signature`, `Unknown` ou future devient une faute source sûre. Le payload Helius `transaction` n'est jamais copié dans l'état Worker : la projection conserve uniquement signature, slot et transaction index, puis réutilise le coordinateur d'hydration commun `getTransaction observed`.
|
||||||
|
|
||||||
|
La clé logique Helius inclut réseau, identités provider/endpoint sûres, commitment et empreinte privée du filtre. Les listes de pubkeys du filtre sont normalisées avant hash afin que leur ordre ne crée pas artificiellement deux sources logiques ; le rôle HTTP d'hydration reste exclu de l'identité live.
|
||||||
|
|
||||||
|
## Contrat de source HTTP Block Polling
|
||||||
|
|
||||||
|
`RawTransactionIngestHttpBlockPollingSource::new` reçoit :
|
||||||
|
|
||||||
|
```text
|
||||||
|
HttpTransportPool
|
||||||
|
HttpRoleName de polling
|
||||||
|
SolanaCommitment Confirmed ou Finalized
|
||||||
|
```
|
||||||
|
|
||||||
|
La construction est sans I/O et vérifie que le rôle HTTP possède, sur un seul réseau, les capacités `getSlot`, `getBlocksWithLimit` et `getBlock`. La cadence est bornée entre 100 ms et 30 s, avec 1 s par défaut ; la découverte est bornée entre 1 et 1024 blocs par cycle, avec 128 par défaut. Ces réglages de cadence ne font pas partie de l'identité logique de la source.
|
||||||
|
|
||||||
|
Au démarrage, le premier `getSlot` fixe la borne inférieure du run. Le Worker ne demande aucun slot antérieur. Chaque cycle relit le tip, découvre les blocs disponibles avec `getBlocksWithLimit`, puis matérialise chaque slot listé par `getBlock observed` en `Full + Base64 + maxSupportedTransactionVersion = 1 + showRewards = false`. Seules les transactions Legacy/V0/V1 explicitement qualifiées entrent directement dans Common RAW.
|
||||||
|
|
||||||
|
Un slot listé dont `getBlock` retourne `null` reste la tête de reprise du cycle suivant ; il n'est ni considéré vide ni marqué settled. Les slots absents de la liste de découverte sont traités comme non produits/skipped pour ce run. Le slot n'est settled qu'après admission réussie de toutes ses transactions. Le polling reste run-local : aucun checkpoint durable, aucun scan avant la borne initiale et aucun Backfill implicite ne sont créés. Les retries/reroutages HTTP restent possédés par `ksp-onchain-transport-lib`.
|
||||||
|
|
||||||
## Runtime et lifecycle
|
## Runtime et lifecycle
|
||||||
|
|
||||||
@@ -71,23 +165,30 @@ Le Worker s'exécute sur le runtime Tokio courant du caller. Il ne crée pas de
|
|||||||
- utiliser la même source via `WorkerSnapshotSource` ;
|
- utiliser la même source via `WorkerSnapshotSource` ;
|
||||||
- attendre le terminal après drain et join des tâches possédées.
|
- attendre le terminal après drain et join des tâches possédées.
|
||||||
|
|
||||||
Le shutdown est borné par `shutdown_drain_timeout`. Les tâches source, hydration et persistence possédées sont drainées ou abort+join avant publication terminale. L'abandon terminal d'une hydration retire son pending run-local sans le convertir artificiellement en travail `settled`.
|
Le shutdown est borné par `shutdown_drain_timeout`. Le supervisor multi-source relaie le stop à toutes les sources et les rejoint avant de rendre son résultat au supervisor Worker ; les tâches source, hydration et persistence possédées sont ensuite drainées ou abort+join avant publication terminale. Si la deadline expire, l'abort du wrapper source détruit aussi son `JoinSet` interne et annule ses tâches imbriquées avant le terminal. Une faute déjà observée n'est pas remplacée par un stop concurrent, sauf le `drain_timeout` terminal lorsqu'une récupération bornée dépasse sa deadline. L'abandon terminal d'une hydration retire son pending run-local sans le convertir artificiellement en travail `settled`.
|
||||||
|
|
||||||
## Admission, coalescence et backpressure
|
## Admission, coalescence et backpressure
|
||||||
|
|
||||||
La queue centrale est un `tokio::sync::mpsc` privé borné par `admission_queue_capacity`. Les sources internes subissent la backpressure ; aucune queue non bornée ni silent drop n'est autorisé.
|
La queue centrale est un `tokio::sync::mpsc` privé borné par `admission_queue_capacity`. Les sources internes subissent la backpressure ; aucune queue non bornée ni silent drop n'est autorisé.
|
||||||
|
|
||||||
La source Yellowstone possède également un coordinateur d'hydration borné :
|
Le Worker possède des coordinateurs source-neutral Yellowstone, Standard Logs et Helius Transaction branchés sur un registre global d'hydration partagé. Standard Block et HTTP Block Polling n'entrent pas dans ce registre lorsqu'une transaction est direct-qualified.
|
||||||
|
|
||||||
|
Les sources reference-bearing reçoivent des quotas déterministes dont la somme reste exactement dans les bornes techniques configurées :
|
||||||
|
|
||||||
```text
|
```text
|
||||||
in-flight hydration <= persistence_concurrency
|
somme pending source signals <= admission_queue_capacity
|
||||||
pending source signals <= 65_536
|
somme hydration tasks in flight <= persistence_concurrency
|
||||||
|
source reference-bearing active => quota pending >= 1 et quota in-flight >= 1
|
||||||
```
|
```
|
||||||
|
|
||||||
Les signaux partageant le même `(network, signature, commitment)` sont coalescés avant le fan-out HTTP. Les provenances utiles restent néanmoins conservées pour les ingress produits après hydration.
|
Pour garantir simultanément ces bornes et l'absence de starvation structurelle, le démarrage échoue avant spawn si le nombre de sources reference-bearing dépasse `admission_queue_capacity` ou `persistence_concurrency`. Un sémaphore global protège en plus l'ouverture effective des hydrations HTTP.
|
||||||
|
|
||||||
|
Les signaux partageant le même `(network, signature, commitment)` sont coalescés cross-source avant le fan-out HTTP. La publication du résultat partagé notifie les followers sous le verrou de registry avant de retirer la clé : une nouvelle génération de leader ne peut donc pas s'intercaler entre retrait et notification. Après canonicalisation, une cache run-local bornée sérialise les acquisitions de même `(network, signature)` : la première passe par l'écriture atomique entity + observation, les suivantes de contenu canonique identique ajoutent uniquement leur observation déterministe. Une divergence de slot, block time, format ou hash canonique devient un content conflict terminal ; aucune majorité, préférence provider ou overwrite n'est appliqué. Le Store conserve son guard durable final.
|
||||||
|
|
||||||
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 :
|
||||||
@@ -110,32 +211,60 @@ 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 :
|
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
|
```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
|
||||||
|
source_active
|
||||||
|
source_reconnecting
|
||||||
|
source_failed
|
||||||
source_state
|
source_state
|
||||||
|
source_failure_total
|
||||||
|
backpressure_wait_total
|
||||||
source_reconnect_total
|
source_reconnect_total
|
||||||
source_replay_attempt_total
|
source_replay_attempt_total
|
||||||
source_continuity_gap_total
|
source_continuity_gap_total
|
||||||
```
|
```
|
||||||
|
|
||||||
`RawTransactionIngestSourceState` distingue :
|
`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.
|
||||||
|
|
||||||
```text
|
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.
|
||||||
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`.
|
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.
|
||||||
|
|
||||||
Le Worker ne lance alors ni Job Backfill ni campagne historique automatique.
|
|
||||||
|
|
||||||
## Persistence Store
|
## Persistence Store
|
||||||
|
|
||||||
@@ -172,7 +301,7 @@ worker_raw_transaction_ingest.source_failed
|
|||||||
worker_raw_transaction_ingest.drain_timeout
|
worker_raw_transaction_ingest.drain_timeout
|
||||||
```
|
```
|
||||||
|
|
||||||
Les diagnostics et `Debug` ne recopient pas de payload RAW, signature, URL, credential, filtre provider, texte backend/provider arbitraire ou client inférieur.
|
Les diagnostics et `Debug` ne recopient pas de payload RAW, signature, URL, credential, filtre provider, texte backend/provider arbitraire ou client inférieur. Les agrégations de compteurs de continuité multi-source utilisent une arithmétique vérifiée ; un overflow devient `counter_exhausted` au lieu d'être saturé silencieusement.
|
||||||
|
|
||||||
## Dépendances
|
## Dépendances
|
||||||
|
|
||||||
@@ -195,9 +324,7 @@ La crate ne dépend pas de Config, Job, `ksp-store-api` directement, backend Sto
|
|||||||
|
|
||||||
La verticale actuelle ne possède pas :
|
La verticale actuelle ne possède pas :
|
||||||
|
|
||||||
- plusieurs sources productives simultanées dans `RawTransactionIngestRuntimeResources` ;
|
- sélection Config interne, reconfiguration dynamique ou policy de failover entre sources ;
|
||||||
- source WS/Helius-specific ou HTTP polling Worker ;
|
|
||||||
- sélection Config interne au Worker ;
|
|
||||||
- checkpoint persistent de processing frontier ;
|
- checkpoint persistent de processing frontier ;
|
||||||
- campagne de réparation historique automatique ;
|
- campagne de réparation historique automatique ;
|
||||||
- application Desk ou process autonome ;
|
- application Desk ou process autonome ;
|
||||||
|
|||||||
@@ -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: 2 -->
|
<!-- version: 12 -->
|
||||||
|
|
||||||
# Utilisation de ksp-worker-raw-transaction-ingest-lib
|
# Utilisation de ksp-worker-raw-transaction-ingest-lib
|
||||||
|
|
||||||
@@ -101,7 +101,127 @@ let handle = match ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestWo
|
|||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
Les deux entrées exigent un runtime Tokio courant et un `Store` portant exactement le même `RawNetworkId` que les settings. Le démarrage avec ressources exige également que la source Yellowstone/HTTP cible ce même réseau.
|
Les deux entrées exigent un runtime Tokio courant et un `Store` portant exactement le même `RawNetworkId` que les settings. Le démarrage avec ressources exige également que chaque source composée cible ce même réseau. L'agrégat accepte 1 à 32 sources logiques et les lance simultanément ; les doublons d'identité et les mélanges de réseaux sont refusés avant spawn. Il n'existe pas de source primaire, standby ou fallback implicite : toute source configurée fait partie du run.
|
||||||
|
|
||||||
|
### Composer plusieurs sources simultanées
|
||||||
|
|
||||||
|
Une fois un premier `RawTransactionIngestRuntimeResources` construit, le caller ajoute les autres sources avec les méthodes `try_push_*` correspondant à leur capability. Exemple conceptuel :
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let mut resources = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::new(yellowstone_source);
|
||||||
|
if let std::result::Result::Err(error) = resources.try_push_standard_logs_source(standard_logs_source) {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
if let std::result::Result::Err(error) = resources.try_push_http_block_polling_source(http_polling_source) {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
Une source standard Solana WS se compose ainsi :
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn standard_logs_runtime_resources(
|
||||||
|
ws_endpoint: ksp_onchain_transport_lib::WsEndpointSettings,
|
||||||
|
filter: ksp_onchain_transport_lib::SolanaLogsSubscribeFilter,
|
||||||
|
commitment: ksp_onchain_transport_lib::SolanaCommitment,
|
||||||
|
http_pool: ksp_onchain_transport_lib::HttpTransportPool,
|
||||||
|
hydration_role: ksp_onchain_transport_lib::HttpRoleName,
|
||||||
|
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources> {
|
||||||
|
let source = match ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestStandardLogsSource::new(
|
||||||
|
ws_endpoint,
|
||||||
|
filter,
|
||||||
|
commitment,
|
||||||
|
http_pool,
|
||||||
|
hydration_role,
|
||||||
|
) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
return std::result::Result::Ok(ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::from_standard_logs_source(source));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`ws_endpoint` doit être un endpoint Transport valide de kind `solana_standard`. Le commitment doit être explicitement `Confirmed` ou `Finalized`. Le pool HTTP doit exposer `getTransaction` via le rôle indiqué sur le même réseau. `All`, `AllWithVotes` et `Mentions(pubkey)` sont acceptés par le contrat Transport ; la valeur du filtre reste privée dans le Worker.
|
||||||
|
|
||||||
|
### Source Standard Block productive
|
||||||
|
|
||||||
|
Une source `blockSubscribe` standard se compose sans pool HTTP :
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn standard_block_runtime_resources(
|
||||||
|
ws_endpoint: ksp_onchain_transport_lib::WsEndpointSettings,
|
||||||
|
filter: ksp_onchain_transport_lib::SolanaBlockSubscribeFilter,
|
||||||
|
commitment: ksp_onchain_transport_lib::SolanaCommitment,
|
||||||
|
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources> {
|
||||||
|
let source = match ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestStandardBlockSource::new(ws_endpoint, filter, commitment) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
return std::result::Result::Ok(ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::from_standard_block_source(source));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Le Worker demande `Base64`, `Full`, `maxSupportedTransactionVersion = 1` et `showRewards = false`. Une transaction n'est RAW-direct que si sa version est explicitement `Legacy`, `0` ou `1`. Une version omise/nulle ou supérieure, `block: null`, une erreur de bloc, un champ transactions absent/nul ou une transaction non Base64 provoque une faute sûre ; ces cas ne sont jamais assimilés à une progression vide.
|
||||||
|
|
||||||
|
### Source Helius Transaction productive
|
||||||
|
|
||||||
|
Une source Helius LaserStream `transactionSubscribe` se compose avec le même pattern caller-owned :
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn helius_transaction_runtime_resources(
|
||||||
|
ws_endpoint: ksp_onchain_transport_lib::WsEndpointSettings,
|
||||||
|
filter: ksp_onchain_transport_lib::HeliusTransactionSubscribeFilter,
|
||||||
|
commitment: ksp_onchain_transport_lib::SolanaCommitment,
|
||||||
|
http_pool: ksp_onchain_transport_lib::HttpTransportPool,
|
||||||
|
hydration_role: ksp_onchain_transport_lib::HttpRoleName,
|
||||||
|
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources> {
|
||||||
|
let source = match ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHeliusTransactionSource::new(
|
||||||
|
ws_endpoint,
|
||||||
|
filter,
|
||||||
|
commitment,
|
||||||
|
http_pool,
|
||||||
|
hydration_role,
|
||||||
|
) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
return std::result::Result::Ok(ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::from_helius_transaction_source(source));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`ws_endpoint` doit être un endpoint Transport de kind `helius_laserstream`. Le caller supérieur résout éventuellement `KSP_SECRET_HELIUS_API_KEY` via Config avant de construire l'endpoint ; le Worker ne lit jamais l'environnement ni Config. Le commitment est limité à `Confirmed`/`Finalized` et la route HTTP doit supporter `getTransaction` sur le même réseau.
|
||||||
|
|
||||||
|
Le Worker demande la forme Helius `Full` avec `Base64`, `showRewards = false` et `maxSupportedTransactionVersion = 1`, mais ne fait pas confiance au nested payload pour construire directement le Common RAW. Il conserve seulement signature/slot/index et hydrate par `getTransaction observed`. Une notification d'une autre forme est fail-closed.
|
||||||
|
|
||||||
|
### Source HTTP Block Polling productive
|
||||||
|
|
||||||
|
Une source HTTP live peut être composée sans WebSocket ni gRPC :
|
||||||
|
|
||||||
|
```rust
|
||||||
|
fn http_block_polling_runtime_resources(
|
||||||
|
http_pool: ksp_onchain_transport_lib::HttpTransportPool,
|
||||||
|
polling_role: ksp_onchain_transport_lib::HttpRoleName,
|
||||||
|
commitment: ksp_onchain_transport_lib::SolanaCommitment,
|
||||||
|
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources> {
|
||||||
|
let source = match ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHttpBlockPollingSource::new(
|
||||||
|
http_pool,
|
||||||
|
polling_role,
|
||||||
|
commitment,
|
||||||
|
) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
return std::result::Result::Ok(ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::from_http_block_polling_source(source));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Le rôle HTTP doit disposer, sur un même réseau, de `getSlot`, `getBlocksWithLimit` et `getBlock`. Le commitment est limité à `Confirmed`/`Finalized`. Par défaut, le Worker interroge toutes les secondes et borne la découverte à 128 blocs par cycle. `new_with_limits` permet de choisir une cadence entre 100 ms et 30 s et une limite entre 1 et 1024 blocs par cycle ; les limites de débit physiques restent celles de Transport.
|
||||||
|
|
||||||
|
Au démarrage du run, la première valeur `getSlot(commitment)` devient la borne inférieure stricte du poller. Il ne demande jamais de slot antérieur. `getBlocksWithLimit` détermine les slots réellement matérialisables, puis `getBlock observed` produit directement le Common RAW en Full/Base64 pour Legacy/V0/V1. Un slot listé dont `getBlock` retourne `null` reste la tête de reprise du prochain cycle et n'est jamais transformé en progression silencieuse.
|
||||||
|
|
||||||
## Préparer la source Yellowstone
|
## Préparer la source Yellowstone
|
||||||
|
|
||||||
@@ -118,18 +238,35 @@ Le caller ne passe pas de signature, `program_id`, plage de slots ou limite hist
|
|||||||
|
|
||||||
## Comprendre la pipeline live
|
## Comprendre la pipeline live
|
||||||
|
|
||||||
Les familles Yellowstone sont traitées ainsi :
|
Les familles productives sont traitées ainsi :
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Transaction -> signal -> HTTP getTransaction -> Common RAW -> admission
|
Yellowstone Transaction -> signal -> HTTP getTransaction -> Common RAW -> admission
|
||||||
TransactionStatus -> signal -> HTTP getTransaction -> Common RAW -> admission
|
Yellowstone TransactionStatus -> signal -> HTTP getTransaction -> Common RAW -> admission
|
||||||
Block -> un signal par transaction -> HTTP getTransaction -> Common RAW -> admission
|
Yellowstone Block -> un signal par transaction -> HTTP getTransaction -> Common RAW -> admission
|
||||||
BlockMeta -> continuity-only
|
Standard WS logsSubscribe -> context.slot + signature -> HTTP getTransaction -> Common RAW -> admission
|
||||||
Slot -> continuity-only
|
Standard WS blockSubscribe -> Full/Base64 Legacy|V0|V1 -> Common RAW direct par transaction -> admission
|
||||||
Account/Ping/Pong/Entry -> sans RAW Transaction dans cette verticale
|
Helius transactionSubscribe -> Full envelope -> signature/slot/index -> HTTP getTransaction -> Common RAW -> admission
|
||||||
|
HTTP live block polling -> getSlot -> getBlocksWithLimit -> getBlock observed -> Common RAW direct Legacy|V0|V1 -> admission
|
||||||
|
Yellowstone BlockMeta -> continuity-only
|
||||||
|
Yellowstone Slot -> continuity-only
|
||||||
|
Yellowstone Account/Ping/Pong/Entry -> sans RAW Transaction dans cette verticale
|
||||||
```
|
```
|
||||||
|
|
||||||
Les signaux de même `(network, signature, commitment)` sont coalescés avant l'hydration HTTP. Le Worker ne possède pas une boucle de retry HTTP : reroutage/retry/backoff restent dans `ksp-onchain-transport-lib`.
|
Les signaux de même `(network, signature, commitment)` sont coalescés globalement avant l'hydration HTTP. Si plusieurs sources produisent ensuite la même transaction canonique, le Worker conserve une seule entité RAW et enregistre séparément les observations déterministes propres à chaque source. Le Worker ne possède pas une boucle de retry HTTP : reroutage/retry/backoff restent dans `ksp-onchain-transport-lib`.
|
||||||
|
|
||||||
|
Les sources qui nécessitent `getTransaction` partagent des quotas bornés et déterministes. Pour une composition valide :
|
||||||
|
|
||||||
|
```text
|
||||||
|
nombre de sources avec hydration <= admission_queue_capacity
|
||||||
|
nombre de sources avec hydration <= persistence_concurrency
|
||||||
|
somme de leurs pending <= admission_queue_capacity
|
||||||
|
somme de leurs tâches d'hydration actives <= persistence_concurrency
|
||||||
|
```
|
||||||
|
|
||||||
|
Ces contraintes garantissent au moins une part à chaque source reference-bearing sans introduire de scheduler pondéré. Si les settings ne permettent pas cette répartition, le démarrage échoue avant spawn avec `runtime_invalid`; le caller doit augmenter la capacité concernée ou réduire le nombre de sources nécessitant une hydration.
|
||||||
|
|
||||||
|
Pour une même identité `(network, signature)`, une divergence canonique de slot, block time, format ou hash est un `content_conflict`. Le Worker ne choisit ni majorité ni provider préféré et n'écrase pas un contenu divergent ; le Store reste l'autorité durable finale du conflit.
|
||||||
|
|
||||||
## Observer le snapshot concret
|
## Observer le snapshot concret
|
||||||
|
|
||||||
@@ -144,6 +281,10 @@ let in_flight = current.in_flight_persistence();
|
|||||||
let hydration_pending = current.hydration_pending();
|
let hydration_pending = current.hydration_pending();
|
||||||
let frontier = current.processing_frontier_slot();
|
let frontier = current.processing_frontier_slot();
|
||||||
let oldest_pending = current.oldest_pending_slot();
|
let oldest_pending = current.oldest_pending_slot();
|
||||||
|
let source_total = current.source_total();
|
||||||
|
let source_active = current.source_active();
|
||||||
|
let source_reconnecting = current.source_reconnecting();
|
||||||
|
let source_failed = current.source_failed();
|
||||||
let source_state = current.source_state();
|
let source_state = current.source_state();
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -190,9 +331,15 @@ 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()` et `hydration_pending()` sont des gauges latest-value. Les compteurs cumulés ne wrapent jamais 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`.
|
||||||
|
|
||||||
## Interpréter la processing frontier
|
## Interpréter la processing frontier
|
||||||
|
|
||||||
@@ -211,7 +358,9 @@ Un `Missing` HTTP règle le signal du point de vue source-processing sans créer
|
|||||||
|
|
||||||
## Interpréter reconnect et replay
|
## Interpréter reconnect et replay
|
||||||
|
|
||||||
`source_state()` peut retourner `Active`, `Reconnecting`, `Closing`, `Closed` ou `Failed` après démarrage de la source productive.
|
`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`.
|
||||||
|
|
||||||
|
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 :
|
||||||
|
|
||||||
@@ -223,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
|
||||||
|
|
||||||
@@ -243,7 +394,7 @@ if accepted {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
`request_stop()` est idempotent. Le terminal n'est publié qu'après le drain borné et la récupération des tâches possédées.
|
`request_stop()` est idempotent. Le terminal n'est publié qu'après le drain borné et la récupération des tâches possédées. Si la deadline de drain expire, toutes les tâches source/persistence encore possédées sont abortées puis jointes avant publication terminale ; une persistence libérée après ce terminal ne peut donc pas produire une complétion tardive. Une faute source déjà observée reste prioritaire face à un stop concurrent, sauf si le drain lui-même expire et devient le terminal `drain_timeout`.
|
||||||
|
|
||||||
## Interpréter les faults
|
## Interpréter les faults
|
||||||
|
|
||||||
@@ -266,8 +417,8 @@ Le pattern attendu est :
|
|||||||
```text
|
```text
|
||||||
Config / application / service owner
|
Config / application / service owner
|
||||||
-> résout endpoints, credentials et rôles
|
-> résout endpoints, credentials et rôles
|
||||||
-> construit YellowstoneGrpcChannel + YellowstoneSubscribeRequest
|
-> construit une source Transport Yellowstone, Standard Logs, Standard Block, Helius Transaction ou HTTP Block Polling
|
||||||
-> construit HttpTransportPool + hydration role
|
-> construit HttpTransportPool + rôle HTTP adapté à la source
|
||||||
-> construit le Store
|
-> construit le Store
|
||||||
-> construit RawTransactionIngestRuntimeResources
|
-> construit RawTransactionIngestRuntimeResources
|
||||||
-> construit RawTransactionIngestSettings
|
-> construit RawTransactionIngestSettings
|
||||||
|
|||||||
1238
crates/ksp-worker-raw-transaction-ingest-lib/src/continuity.rs
Normal file
1238
crates/ksp-worker-raw-transaction-ingest-lib/src/continuity.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,24 +1,28 @@
|
|||||||
// file: crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
|
// file: crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
|
||||||
// version: 17
|
// version: 33
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![deny(unreachable_pub)]
|
#![deny(unreachable_pub)]
|
||||||
#![forbid(unsafe_code)]
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
//! Source-neutral runtime foundation for continuous KSP RAW transaction ingestion.
|
//! Source-neutral runtime foundation for continuous KSP RAW transaction ingestion.
|
||||||
|
//! Owned source, hydration and persistence tasks are bounded and joined before terminal publication.
|
||||||
//!
|
//!
|
||||||
//! This tranche owns the concrete Worker family identity, validated technical settings
|
//! This tranche owns the concrete Worker family identity, validated technical settings
|
||||||
//! and the caller-runtime-owned lifecycle with private child-task supervision. This tranche also
|
//! and the caller-runtime-owned lifecycle with private child-task supervision. This tranche also
|
||||||
//! owns bounded source-neutral admission, common RAW canonicalization/assembly and backend-neutral
|
//! owns bounded source-neutral admission, common RAW canonicalization/assembly and backend-neutral
|
||||||
//! Store persistence in normal mode plus concrete latest-value snapshots projected onto Worker API;
|
//! Store persistence in normal mode plus concrete latest-value snapshots projected onto Worker API. The bounded 1..32 caller-composed aggregate starts
|
||||||
//! the validated Yellowstone/HTTP runtime-resource contract now drives one productive supervised source task.
|
//! all validated Yellowstone, Standard Logs, Standard Block, Helius Transaction and HTTP live block polling sources under one private supervisor. Reference-bearing
|
||||||
//! Transaction/TransactionStatus/Block updates feed bounded HTTP `getTransaction` hydration and the existing central
|
//! sources share bounded cross-source hydration and fairness budgets; direct-qualified Standard Block and HTTP polling transactions enter the existing central
|
||||||
//! admission path; BlockMeta/Slot remain continuity-only signals. Hydration is coalesced by network/signature/commitment
|
//! admission path directly. Public snapshots expose only source-neutral aggregate counts/state and conservative Worker health. Yellowstone
|
||||||
//! under bounded in-flight and pending budgets. A bounded run-local processing frontier projects hydration pending,
|
//! Transaction/TransactionStatus/Block and standard logs notifications become signature/slot references; BlockMeta/Slot remain continuity-only signals.
|
||||||
//! oldest pending slot and highest unblocked actually observed slot. The productive source now also projects safe Transport reconnect/replay state and faults
|
//! Hydration is coalesced by network/signature/commitment under bounded in-flight and pending budgets. A bounded run-local processing frontier projects
|
||||||
//! conservatively when Transport proves a replay-retention continuity gap; history remediation remains outside this crate.
|
//! hydration pending, oldest pending slot and highest unblocked actually observed slot. The productive source also projects safe Transport reconnect/replay
|
||||||
|
//! 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;
|
||||||
@@ -49,8 +53,30 @@ pub use self::runtime::RawTransactionIngestHandle;
|
|||||||
pub use self::runtime::RawTransactionIngestTerminalFuture;
|
pub use self::runtime::RawTransactionIngestTerminalFuture;
|
||||||
/// Entry point owning synchronous validation and task launch for one RAW transaction ingest Worker run.
|
/// Entry point owning synchronous validation and task launch for one RAW transaction ingest Worker run.
|
||||||
pub use self::runtime::RawTransactionIngestWorker;
|
pub use self::runtime::RawTransactionIngestWorker;
|
||||||
/// Caller-composed runtime resources for the first Yellowstone + HTTP source family.
|
/// Default interval between HTTP live block polling cycles.
|
||||||
|
pub use self::runtime_resources::DEFAULT_RAW_TRANSACTION_INGEST_HTTP_POLL_INTERVAL;
|
||||||
|
/// Default maximum number of confirmed blocks discovered during one HTTP live block polling cycle.
|
||||||
|
pub use self::runtime_resources::DEFAULT_RAW_TRANSACTION_INGEST_HTTP_POLL_MAX_BLOCKS_PER_CYCLE;
|
||||||
|
/// Maximum interval accepted between HTTP live block polling cycles.
|
||||||
|
pub use self::runtime_resources::MAX_RAW_TRANSACTION_INGEST_HTTP_POLL_INTERVAL;
|
||||||
|
/// Maximum number of confirmed blocks accepted during one HTTP live block polling cycle.
|
||||||
|
pub use self::runtime_resources::MAX_RAW_TRANSACTION_INGEST_HTTP_POLL_MAX_BLOCKS_PER_CYCLE;
|
||||||
|
/// Maximum number of logical live sources accepted by one runtime-resource aggregate.
|
||||||
|
pub use self::runtime_resources::MAX_RAW_TRANSACTION_INGEST_LIVE_SOURCES;
|
||||||
|
/// Minimum interval accepted between HTTP live block polling cycles.
|
||||||
|
pub use self::runtime_resources::MIN_RAW_TRANSACTION_INGEST_HTTP_POLL_INTERVAL;
|
||||||
|
/// Minimum number of confirmed blocks accepted during one HTTP live block polling cycle.
|
||||||
|
pub use self::runtime_resources::MIN_RAW_TRANSACTION_INGEST_HTTP_POLL_MAX_BLOCKS_PER_CYCLE;
|
||||||
|
/// Validated Helius `transactionSubscribe` + HTTP hydration source contract owned by the continuous RAW transaction ingest Worker.
|
||||||
|
pub use self::runtime_resources::RawTransactionIngestHeliusTransactionSource;
|
||||||
|
/// Validated standard Solana HTTP live block polling source contract owned by the continuous RAW transaction ingest Worker.
|
||||||
|
pub use self::runtime_resources::RawTransactionIngestHttpBlockPollingSource;
|
||||||
|
/// Caller-composed bounded runtime resources for supported continuous RAW transaction live-source families.
|
||||||
pub use self::runtime_resources::RawTransactionIngestRuntimeResources;
|
pub use self::runtime_resources::RawTransactionIngestRuntimeResources;
|
||||||
|
/// Validated standard Solana `blockSubscribe` direct RAW source contract owned by the continuous RAW transaction ingest Worker.
|
||||||
|
pub use self::runtime_resources::RawTransactionIngestStandardBlockSource;
|
||||||
|
/// Validated standard Solana `logsSubscribe` + HTTP hydration source contract owned by the continuous RAW transaction ingest Worker.
|
||||||
|
pub use self::runtime_resources::RawTransactionIngestStandardLogsSource;
|
||||||
/// Validated Yellowstone + HTTP source contract owned by the continuous RAW transaction ingest Worker.
|
/// Validated Yellowstone + HTTP source contract owned by the continuous RAW transaction ingest Worker.
|
||||||
pub use self::runtime_resources::RawTransactionIngestYellowstoneSource;
|
pub use self::runtime_resources::RawTransactionIngestYellowstoneSource;
|
||||||
/// Default bounded admission queue capacity for one RAW transaction ingest Worker.
|
/// Default bounded admission queue capacity for one RAW transaction ingest Worker.
|
||||||
@@ -73,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.
|
||||||
@@ -86,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.
|
||||||
@@ -100,12 +150,18 @@ pub(crate) use self::error::store_error;
|
|||||||
pub(crate) use self::persistence::RawTransactionIngestEntityPersistence;
|
pub(crate) use self::persistence::RawTransactionIngestEntityPersistence;
|
||||||
/// Observation disposition produced by one successful Worker Store persistence attempt.
|
/// Observation disposition produced by one successful Worker Store persistence attempt.
|
||||||
pub(crate) use self::persistence::RawTransactionIngestObservationPersistence;
|
pub(crate) use self::persistence::RawTransactionIngestObservationPersistence;
|
||||||
|
/// Private bounded run-local cache serializing repeated canonical identities before Store writes.
|
||||||
|
pub(crate) use self::persistence::RawTransactionIngestPersistenceConvergence;
|
||||||
/// Classified successful outcome of one atomic Worker Store persistence attempt.
|
/// Classified successful outcome of one atomic Worker Store persistence attempt.
|
||||||
pub(crate) use self::persistence::RawTransactionIngestPersistenceOutcome;
|
pub(crate) use self::persistence::RawTransactionIngestPersistenceOutcome;
|
||||||
/// Private backend-neutral Store persistence port used by the Worker and deterministic tests.
|
/// Private backend-neutral Store persistence port used by the Worker and deterministic tests.
|
||||||
pub(crate) use self::persistence::RawTransactionIngestPersistencePort;
|
pub(crate) use self::persistence::RawTransactionIngestPersistencePort;
|
||||||
/// Persists one already-canonical Worker acquisition through the private Store port in `Normal` mode.
|
/// Persists one already-canonical Worker acquisition through the private Store port in `Normal` mode.
|
||||||
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.
|
||||||
|
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.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-worker-raw-transaction-ingest-lib/src/persistence.rs
|
// file: crates/ksp-worker-raw-transaction-ingest-lib/src/persistence.rs
|
||||||
// version: 1
|
// version: 4
|
||||||
|
|
||||||
/// Canonical entity disposition produced by one Worker Store persistence attempt.
|
/// Canonical entity disposition produced by one Worker Store persistence attempt.
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
@@ -56,6 +56,12 @@ pub(crate) trait RawTransactionIngestPersistencePort: std::marker::Send + std::m
|
|||||||
observation: ksp_store_lib::RawTransactionObservation,
|
observation: ksp_store_lib::RawTransactionObservation,
|
||||||
mode: ksp_store_lib::RawTransactionAcquisitionMode,
|
mode: ksp_store_lib::RawTransactionAcquisitionMode,
|
||||||
) -> ksp_store_lib::StoreApiFuture<'a, ksp_store_lib::Result<ksp_store_lib::RawAcquisitionWriteOutcome>>;
|
) -> ksp_store_lib::StoreApiFuture<'a, ksp_store_lib::Result<ksp_store_lib::RawAcquisitionWriteOutcome>>;
|
||||||
|
|
||||||
|
/// Records one additional observation for an already durable canonical RAW transaction.
|
||||||
|
fn record_observation<'a>(
|
||||||
|
&'a self,
|
||||||
|
observation: ksp_store_lib::RawTransactionObservation,
|
||||||
|
) -> ksp_store_lib::StoreApiFuture<'a, ksp_store_lib::Result<ksp_store_lib::RawObservationWriteOutcome>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl crate::RawTransactionIngestPersistencePort for ksp_store_lib::Store {
|
impl crate::RawTransactionIngestPersistencePort for ksp_store_lib::Store {
|
||||||
@@ -71,6 +77,77 @@ impl crate::RawTransactionIngestPersistencePort for ksp_store_lib::Store {
|
|||||||
) -> ksp_store_lib::StoreApiFuture<'a, ksp_store_lib::Result<ksp_store_lib::RawAcquisitionWriteOutcome>> {
|
) -> ksp_store_lib::StoreApiFuture<'a, ksp_store_lib::Result<ksp_store_lib::RawAcquisitionWriteOutcome>> {
|
||||||
return ksp_store_lib::RawTransactionWrite::persist_raw_transaction_acquisition(self, transaction, observation, mode);
|
return ksp_store_lib::RawTransactionWrite::persist_raw_transaction_acquisition(self, transaction, observation, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn record_observation<'a>(
|
||||||
|
&'a self,
|
||||||
|
observation: ksp_store_lib::RawTransactionObservation,
|
||||||
|
) -> ksp_store_lib::StoreApiFuture<'a, ksp_store_lib::Result<ksp_store_lib::RawObservationWriteOutcome>> {
|
||||||
|
return ksp_store_lib::RawTransactionObservationWrite::record_raw_transaction_observation(self, observation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Eq, Ord, PartialEq, PartialOrd)]
|
||||||
|
struct RawTransactionIngestPersistenceKey {
|
||||||
|
network: ksp_store_lib::RawNetworkId,
|
||||||
|
signature: ksp_store_lib::RawTransactionSignature,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Eq, PartialEq)]
|
||||||
|
struct RawTransactionIngestCanonicalState {
|
||||||
|
block_time: std::option::Option<ksp_store_lib::RawTimestamp>,
|
||||||
|
content_hash: ksp_store_lib::RawContentHash,
|
||||||
|
format_id: ksp_store_lib::RawFormatId,
|
||||||
|
format_version: u32,
|
||||||
|
slot: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Private bounded run-local cache serializing repeated canonical identities before Store writes.
|
||||||
|
pub(crate) struct RawTransactionIngestPersistenceConvergence {
|
||||||
|
max_entries: usize,
|
||||||
|
entries: std::sync::Mutex<
|
||||||
|
std::collections::BTreeMap<
|
||||||
|
RawTransactionIngestPersistenceKey,
|
||||||
|
std::sync::Arc<tokio::sync::Mutex<std::option::Option<RawTransactionIngestCanonicalState>>>,
|
||||||
|
>,
|
||||||
|
>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl crate::RawTransactionIngestPersistenceConvergence {
|
||||||
|
/// Creates one bounded run-local convergence cache using the caller-provided effective runtime bound.
|
||||||
|
#[must_use]
|
||||||
|
pub(crate) fn new(max_entries: usize) -> Self {
|
||||||
|
return Self { max_entries, entries: std::sync::Mutex::new(std::collections::BTreeMap::new()) };
|
||||||
|
}
|
||||||
|
|
||||||
|
fn entry(
|
||||||
|
&self,
|
||||||
|
key: RawTransactionIngestPersistenceKey,
|
||||||
|
) -> std::option::Option<std::sync::Arc<tokio::sync::Mutex<std::option::Option<RawTransactionIngestCanonicalState>>>> {
|
||||||
|
let mut entries = match self.entries.lock() {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(poisoned) => poisoned.into_inner(),
|
||||||
|
};
|
||||||
|
if let std::option::Option::Some(entry) = entries.get(&key) {
|
||||||
|
return std::option::Option::Some(std::sync::Arc::clone(entry));
|
||||||
|
}
|
||||||
|
if entries.len() >= self.max_entries {
|
||||||
|
let removable = entries.iter().find_map(|(candidate, entry)| {
|
||||||
|
if std::sync::Arc::strong_count(entry) == 1 {
|
||||||
|
return std::option::Option::Some(candidate.clone());
|
||||||
|
}
|
||||||
|
return std::option::Option::None;
|
||||||
|
});
|
||||||
|
if let std::option::Option::Some(removable) = removable {
|
||||||
|
entries.remove(&removable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if entries.len() >= self.max_entries {
|
||||||
|
return std::option::Option::None;
|
||||||
|
}
|
||||||
|
let entry = std::sync::Arc::new(tokio::sync::Mutex::new(std::option::Option::None));
|
||||||
|
entries.insert(key, std::sync::Arc::clone(&entry));
|
||||||
|
return std::option::Option::Some(entry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Persists one already-canonical Worker acquisition through the private Store port in `Normal` mode.
|
/// Persists one already-canonical Worker acquisition through the private Store port in `Normal` mode.
|
||||||
@@ -96,6 +173,62 @@ where
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Persists one canonical acquisition through the bounded cross-source convergence cache.
|
||||||
|
pub(crate) async fn persist_raw_transaction_ingest_converged_acquisition<P>(
|
||||||
|
port: &P,
|
||||||
|
acquisition: ksp_raw_transaction_lib::RawTransactionAcquisition,
|
||||||
|
convergence: &crate::RawTransactionIngestPersistenceConvergence,
|
||||||
|
) -> ksp_core_lib::Result<crate::RawTransactionIngestPersistenceOutcome>
|
||||||
|
where
|
||||||
|
P: crate::RawTransactionIngestPersistencePort + ?Sized,
|
||||||
|
{
|
||||||
|
let reference = acquisition.transaction().reference().clone();
|
||||||
|
if !port.network_matches(reference.network()) {
|
||||||
|
return std::result::Result::Err(crate::runtime_error("persistence.store_network_mismatch"));
|
||||||
|
}
|
||||||
|
if acquisition.observation().transaction() != &reference {
|
||||||
|
return std::result::Result::Err(crate::runtime_error("persistence.acquisition_reference_mismatch"));
|
||||||
|
}
|
||||||
|
let canonical_state = canonical_state(acquisition.transaction());
|
||||||
|
let key = RawTransactionIngestPersistenceKey { network: reference.network().clone(), signature: reference.signature() };
|
||||||
|
let entry = convergence.entry(key);
|
||||||
|
let entry = match entry {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return crate::persist_raw_transaction_ingest_acquisition(port, acquisition).await,
|
||||||
|
};
|
||||||
|
let mut known_state = entry.lock().await;
|
||||||
|
if let std::option::Option::Some(known_state_value) = known_state.as_ref() {
|
||||||
|
if known_state_value != &canonical_state {
|
||||||
|
return std::result::Result::Err(crate::content_conflict_error());
|
||||||
|
}
|
||||||
|
let (_transaction, observation) = acquisition.into_parts();
|
||||||
|
let result = port.record_observation(observation).await;
|
||||||
|
return match result {
|
||||||
|
std::result::Result::Ok(outcome) => map_additional_observation_outcome(outcome),
|
||||||
|
std::result::Result::Err(error) => map_store_error(error),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let outcome = crate::persist_raw_transaction_ingest_acquisition(port, acquisition).await;
|
||||||
|
let outcome = match outcome {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
if outcome.entity() != crate::RawTransactionIngestEntityPersistence::SkippedPurged {
|
||||||
|
*known_state = std::option::Option::Some(canonical_state);
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(outcome);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn canonical_state(transaction: &ksp_store_lib::RawTransaction) -> RawTransactionIngestCanonicalState {
|
||||||
|
return RawTransactionIngestCanonicalState {
|
||||||
|
block_time: transaction.block_time(),
|
||||||
|
content_hash: transaction.payload().content_hash(),
|
||||||
|
format_id: transaction.payload().format_id().clone(),
|
||||||
|
format_version: transaction.payload().format_version(),
|
||||||
|
slot: transaction.slot(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
fn map_store_error(error: ksp_core_lib::Error) -> ksp_core_lib::Result<crate::RawTransactionIngestPersistenceOutcome> {
|
fn map_store_error(error: ksp_core_lib::Error) -> ksp_core_lib::Result<crate::RawTransactionIngestPersistenceOutcome> {
|
||||||
if error.code() == ksp_store_lib::ERROR_CODE_RAW_CONFLICT {
|
if error.code() == ksp_store_lib::ERROR_CODE_RAW_CONFLICT {
|
||||||
return std::result::Result::Err(crate::content_conflict_error());
|
return std::result::Result::Err(crate::content_conflict_error());
|
||||||
@@ -133,6 +266,25 @@ fn map_store_outcome(outcome: ksp_store_lib::RawAcquisitionWriteOutcome) -> ksp_
|
|||||||
return std::result::Result::Err(crate::runtime_error("persistence.store_outcome_invalid"));
|
return std::result::Result::Err(crate::runtime_error("persistence.store_outcome_invalid"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn map_additional_observation_outcome(
|
||||||
|
outcome: ksp_store_lib::RawObservationWriteOutcome,
|
||||||
|
) -> ksp_core_lib::Result<crate::RawTransactionIngestPersistenceOutcome> {
|
||||||
|
return match outcome {
|
||||||
|
ksp_store_lib::RawObservationWriteOutcome::Inserted => std::result::Result::Ok(crate::RawTransactionIngestPersistenceOutcome {
|
||||||
|
entity: crate::RawTransactionIngestEntityPersistence::AlreadyPresent,
|
||||||
|
observation: crate::RawTransactionIngestObservationPersistence::Inserted,
|
||||||
|
}),
|
||||||
|
ksp_store_lib::RawObservationWriteOutcome::AlreadyPresent => std::result::Result::Ok(crate::RawTransactionIngestPersistenceOutcome {
|
||||||
|
entity: crate::RawTransactionIngestEntityPersistence::AlreadyPresent,
|
||||||
|
observation: crate::RawTransactionIngestObservationPersistence::AlreadyPresent,
|
||||||
|
}),
|
||||||
|
ksp_store_lib::RawObservationWriteOutcome::NotRecorded => {
|
||||||
|
std::result::Result::Err(crate::runtime_error("persistence.additional_observation_not_recorded"))
|
||||||
|
},
|
||||||
|
_ => std::result::Result::Err(crate::runtime_error("persistence.additional_observation_outcome_invalid")),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[path = "../unit_tests/persistence.rs"]
|
#[path = "../unit_tests/persistence.rs"]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|||||||
@@ -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: 11
|
// 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>>;
|
||||||
@@ -97,7 +97,10 @@ impl crate::RawTransactionIngestWorker {
|
|||||||
return start_foundation(settings, runtime, std::option::Option::Some(store));
|
return start_foundation(settings, runtime, std::option::Option::Some(store));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Starts one Worker with caller-composed runtime resources and one supervised productive Yellowstone source task.
|
/// Starts one Worker with caller-composed runtime resources.
|
||||||
|
///
|
||||||
|
/// Every validated source in the bounded runtime-resource aggregate is started concurrently under one private source supervisor. Any configured source
|
||||||
|
/// failure remains terminal for the Worker because this release does not infer equivalent coverage or failover between source families.
|
||||||
pub fn start_with_runtime_resources(
|
pub fn start_with_runtime_resources(
|
||||||
settings: crate::RawTransactionIngestSettings,
|
settings: crate::RawTransactionIngestSettings,
|
||||||
store: std::sync::Arc<ksp_store_lib::Store>,
|
store: std::sync::Arc<ksp_store_lib::Store>,
|
||||||
@@ -114,7 +117,7 @@ impl crate::RawTransactionIngestWorker {
|
|||||||
if let std::result::Result::Err(error) = runtime_resources.validate_network(settings.network()) {
|
if let std::result::Result::Err(error) = runtime_resources.validate_network(settings.network()) {
|
||||||
return std::result::Result::Err(error);
|
return std::result::Result::Err(error);
|
||||||
}
|
}
|
||||||
let source = runtime_resources.into_yellowstone_source();
|
let source_total = runtime_resources.source_count();
|
||||||
let source_settings = settings.clone();
|
let source_settings = settings.clone();
|
||||||
let (processing_frontier_sender, processing_frontier_receiver) =
|
let (processing_frontier_sender, processing_frontier_receiver) =
|
||||||
tokio::sync::watch::channel(crate::RawTransactionIngestProcessingFrontierProjection::empty());
|
tokio::sync::watch::channel(crate::RawTransactionIngestProcessingFrontierProjection::empty());
|
||||||
@@ -124,9 +127,10 @@ impl crate::RawTransactionIngestWorker {
|
|||||||
runtime,
|
runtime,
|
||||||
std::option::Option::Some(port),
|
std::option::Option::Some(port),
|
||||||
std::option::Option::Some(processing_frontier_receiver),
|
std::option::Option::Some(processing_frontier_receiver),
|
||||||
|
source_total,
|
||||||
move |children, stop_receiver, admission_sender| {
|
move |children, stop_receiver, admission_sender| {
|
||||||
let _abort_handle = children.spawn(async move {
|
let _abort_handle = children.spawn(async move {
|
||||||
return source.run(source_settings, stop_receiver, admission_sender, processing_frontier_sender).await;
|
return runtime_resources.run_live_sources(source_settings, stop_receiver, admission_sender, processing_frontier_sender).await;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -187,6 +191,7 @@ async fn drain_admission_and_persistence(
|
|||||||
lifecycle: &ksp_worker_api::WorkerLifecycle,
|
lifecycle: &ksp_worker_api::WorkerLifecycle,
|
||||||
admission: &mut crate::RawTransactionAdmission,
|
admission: &mut crate::RawTransactionAdmission,
|
||||||
persistence: &mut PersistenceTasks,
|
persistence: &mut PersistenceTasks,
|
||||||
|
persistence_convergence: &std::sync::Arc<crate::RawTransactionIngestPersistenceConvergence>,
|
||||||
port: &std::option::Option<PersistencePort>,
|
port: &std::option::Option<PersistencePort>,
|
||||||
snapshots: &mut crate::RawTransactionIngestSnapshotPublisher,
|
snapshots: &mut crate::RawTransactionIngestSnapshotPublisher,
|
||||||
) -> std::option::Option<ksp_core_lib::ErrorCode> {
|
) -> std::option::Option<ksp_core_lib::ErrorCode> {
|
||||||
@@ -205,7 +210,7 @@ async fn drain_admission_and_persistence(
|
|||||||
let backpressure_wait_observed = admission.take_backpressure_wait_observed();
|
let backpressure_wait_observed = admission.take_backpressure_wait_observed();
|
||||||
match received {
|
match received {
|
||||||
std::result::Result::Ok(std::option::Option::Some(acquisition)) => {
|
std::result::Result::Ok(std::option::Option::Some(acquisition)) => {
|
||||||
let spawned = spawn_persistence(persistence, port, acquisition);
|
let spawned = spawn_persistence(persistence, persistence_convergence, port, acquisition);
|
||||||
let published = snapshots.record_admission_success(lifecycle.state(), admission.queue_depth(), persistence.len(), backpressure_wait_observed);
|
let published = snapshots.record_admission_success(lifecycle.state(), admission.queue_depth(), persistence.len(), backpressure_wait_observed);
|
||||||
if !spawned && fault.is_none() {
|
if !spawned && fault.is_none() {
|
||||||
fault = std::option::Option::Some(crate::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID);
|
fault = std::option::Option::Some(crate::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID);
|
||||||
@@ -245,11 +250,12 @@ async fn drain_owned_work(
|
|||||||
children: &mut SourceTasks,
|
children: &mut SourceTasks,
|
||||||
admission: &mut crate::RawTransactionAdmission,
|
admission: &mut crate::RawTransactionAdmission,
|
||||||
persistence: &mut PersistenceTasks,
|
persistence: &mut PersistenceTasks,
|
||||||
|
persistence_convergence: &std::sync::Arc<crate::RawTransactionIngestPersistenceConvergence>,
|
||||||
port: &std::option::Option<PersistencePort>,
|
port: &std::option::Option<PersistencePort>,
|
||||||
snapshots: &mut crate::RawTransactionIngestSnapshotPublisher,
|
snapshots: &mut crate::RawTransactionIngestSnapshotPublisher,
|
||||||
) -> std::option::Option<ksp_core_lib::ErrorCode> {
|
) -> std::option::Option<ksp_core_lib::ErrorCode> {
|
||||||
let drain = async {
|
let drain = async {
|
||||||
let mut fault = drain_admission_and_persistence(settings, lifecycle, admission, persistence, port, snapshots).await;
|
let mut fault = drain_admission_and_persistence(settings, lifecycle, admission, persistence, persistence_convergence, port, snapshots).await;
|
||||||
let child_fault = drain_children(lifecycle.state(), children, snapshots).await;
|
let child_fault = drain_children(lifecycle.state(), children, snapshots).await;
|
||||||
fault = merge_fault(fault, child_fault);
|
fault = merge_fault(fault, child_fault);
|
||||||
return fault;
|
return fault;
|
||||||
@@ -337,13 +343,16 @@ fn source_completion(
|
|||||||
in_flight_persistence: usize,
|
in_flight_persistence: usize,
|
||||||
snapshots: &mut crate::RawTransactionIngestSnapshotPublisher,
|
snapshots: &mut crate::RawTransactionIngestSnapshotPublisher,
|
||||||
) -> std::option::Option<ksp_core_lib::ErrorCode> {
|
) -> std::option::Option<ksp_core_lib::ErrorCode> {
|
||||||
match joined {
|
let source_code = match joined {
|
||||||
std::result::Result::Ok(std::result::Result::Ok(())) => return std::option::Option::None,
|
std::result::Result::Ok(std::result::Result::Ok(())) => return std::option::Option::None,
|
||||||
std::result::Result::Ok(std::result::Result::Err(_)) | std::result::Result::Err(_) => {},
|
std::result::Result::Ok(std::result::Result::Err(error)) if error.code() == crate::ERROR_CODE_RAW_TRANSACTION_INGEST_COUNTER_EXHAUSTED => {
|
||||||
}
|
crate::ERROR_CODE_RAW_TRANSACTION_INGEST_COUNTER_EXHAUSTED
|
||||||
|
},
|
||||||
|
std::result::Result::Ok(std::result::Result::Err(_)) | std::result::Result::Err(_) => crate::ERROR_CODE_RAW_TRANSACTION_INGEST_SOURCE_FAILED,
|
||||||
|
};
|
||||||
let published = snapshots.record_source_failure(state, admission_queue_depth, in_flight_persistence);
|
let published = snapshots.record_source_failure(state, admission_queue_depth, in_flight_persistence);
|
||||||
return match published {
|
return match published {
|
||||||
std::result::Result::Ok(()) => std::option::Option::Some(crate::ERROR_CODE_RAW_TRANSACTION_INGEST_SOURCE_FAILED),
|
std::result::Result::Ok(()) => std::option::Option::Some(source_code),
|
||||||
std::result::Result::Err(error) => std::option::Option::Some(error.code()),
|
std::result::Result::Err(error) => std::option::Option::Some(error.code()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -378,6 +387,9 @@ async fn run_supervisor<Spawner>(
|
|||||||
}
|
}
|
||||||
let mut children = SourceTasks::new();
|
let mut children = SourceTasks::new();
|
||||||
let mut persistence = PersistenceTasks::new();
|
let mut persistence = PersistenceTasks::new();
|
||||||
|
let persistence_convergence = std::sync::Arc::new(crate::RawTransactionIngestPersistenceConvergence::new(
|
||||||
|
settings.admission_queue_capacity().max(settings.persistence_concurrency()),
|
||||||
|
));
|
||||||
let (source_stop_sender, source_stop_receiver) = tokio::sync::watch::channel(false);
|
let (source_stop_sender, source_stop_receiver) = tokio::sync::watch::channel(false);
|
||||||
let (mut admission, admission_sender) = crate::RawTransactionAdmission::new(settings.admission_queue_capacity());
|
let (mut admission, admission_sender) = crate::RawTransactionAdmission::new(settings.admission_queue_capacity());
|
||||||
source_spawner(&mut children, source_stop_receiver, admission_sender);
|
source_spawner(&mut children, source_stop_receiver, admission_sender);
|
||||||
@@ -389,6 +401,7 @@ async fn run_supervisor<Spawner>(
|
|||||||
&mut children,
|
&mut children,
|
||||||
&mut admission,
|
&mut admission,
|
||||||
&mut persistence,
|
&mut persistence,
|
||||||
|
&persistence_convergence,
|
||||||
&port,
|
&port,
|
||||||
&mut snapshots,
|
&mut snapshots,
|
||||||
&mut processing_frontier_receiver,
|
&mut processing_frontier_receiver,
|
||||||
@@ -397,7 +410,8 @@ async fn run_supervisor<Spawner>(
|
|||||||
source_stop_sender.send_replace(true);
|
source_stop_sender.send_replace(true);
|
||||||
let stopping_fault = begin_stopping(&mut lifecycle, &mut snapshots, admission.queue_depth(), persistence.len());
|
let stopping_fault = begin_stopping(&mut lifecycle, &mut snapshots, admission.queue_depth(), persistence.len());
|
||||||
fault = merge_fault(fault, stopping_fault);
|
fault = merge_fault(fault, stopping_fault);
|
||||||
let drain_fault = drain_owned_work(&settings, &lifecycle, &mut children, &mut admission, &mut persistence, &port, &mut snapshots).await;
|
let drain_fault =
|
||||||
|
drain_owned_work(&settings, &lifecycle, &mut children, &mut admission, &mut persistence, &persistence_convergence, &port, &mut snapshots).await;
|
||||||
match drain_fault {
|
match drain_fault {
|
||||||
std::option::Option::Some(code) if code == crate::ERROR_CODE_RAW_TRANSACTION_INGEST_DRAIN_TIMEOUT => {
|
std::option::Option::Some(code) if code == crate::ERROR_CODE_RAW_TRANSACTION_INGEST_DRAIN_TIMEOUT => {
|
||||||
fault = std::option::Option::Some(code);
|
fault = std::option::Option::Some(code);
|
||||||
@@ -416,6 +430,7 @@ async fn run_supervisor<Spawner>(
|
|||||||
|
|
||||||
fn spawn_persistence(
|
fn spawn_persistence(
|
||||||
persistence: &mut PersistenceTasks,
|
persistence: &mut PersistenceTasks,
|
||||||
|
persistence_convergence: &std::sync::Arc<crate::RawTransactionIngestPersistenceConvergence>,
|
||||||
port: &std::option::Option<PersistencePort>,
|
port: &std::option::Option<PersistencePort>,
|
||||||
acquisition: ksp_raw_transaction_lib::RawTransactionAcquisition,
|
acquisition: ksp_raw_transaction_lib::RawTransactionAcquisition,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
@@ -423,8 +438,9 @@ fn spawn_persistence(
|
|||||||
std::option::Option::Some(value) => std::sync::Arc::clone(value),
|
std::option::Option::Some(value) => std::sync::Arc::clone(value),
|
||||||
std::option::Option::None => return false,
|
std::option::Option::None => return false,
|
||||||
};
|
};
|
||||||
|
let persistence_convergence = std::sync::Arc::clone(persistence_convergence);
|
||||||
let _abort_handle = persistence.spawn(async move {
|
let _abort_handle = persistence.spawn(async move {
|
||||||
return crate::persist_raw_transaction_ingest_acquisition(port.as_ref(), acquisition).await;
|
return crate::persist_raw_transaction_ingest_converged_acquisition(port.as_ref(), acquisition, persistence_convergence.as_ref()).await;
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -447,7 +463,7 @@ where
|
|||||||
Spawner:
|
Spawner:
|
||||||
FnOnce(&mut SourceTasks, tokio::sync::watch::Receiver<bool>, tokio::sync::mpsc::Sender<crate::RawTransactionIngress>) + std::marker::Send + 'static,
|
FnOnce(&mut SourceTasks, tokio::sync::watch::Receiver<bool>, tokio::sync::mpsc::Sender<crate::RawTransactionIngress>) + std::marker::Send + 'static,
|
||||||
{
|
{
|
||||||
return start_foundation_with_port_source_spawner_and_frontier(settings, runtime, port, std::option::Option::None, source_spawner);
|
return start_foundation_with_port_source_spawner_and_frontier(settings, runtime, port, std::option::Option::None, 0, source_spawner);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_foundation_with_port_source_spawner_and_frontier<Spawner>(
|
fn start_foundation_with_port_source_spawner_and_frontier<Spawner>(
|
||||||
@@ -455,6 +471,7 @@ fn start_foundation_with_port_source_spawner_and_frontier<Spawner>(
|
|||||||
runtime: tokio::runtime::Handle,
|
runtime: tokio::runtime::Handle,
|
||||||
port: std::option::Option<PersistencePort>,
|
port: std::option::Option<PersistencePort>,
|
||||||
processing_frontier_receiver: std::option::Option<ProcessingFrontierReceiver>,
|
processing_frontier_receiver: std::option::Option<ProcessingFrontierReceiver>,
|
||||||
|
source_total: usize,
|
||||||
source_spawner: Spawner,
|
source_spawner: Spawner,
|
||||||
) -> ksp_core_lib::Result<crate::RawTransactionIngestHandle>
|
) -> ksp_core_lib::Result<crate::RawTransactionIngestHandle>
|
||||||
where
|
where
|
||||||
@@ -471,7 +488,7 @@ where
|
|||||||
}
|
}
|
||||||
let stop_token = ksp_worker_api::WorkerStopToken::new();
|
let stop_token = ksp_worker_api::WorkerStopToken::new();
|
||||||
let (stop_sender, stop_receiver) = tokio::sync::watch::channel(false);
|
let (stop_sender, stop_receiver) = tokio::sync::watch::channel(false);
|
||||||
let (snapshots, snapshot_source) = crate::RawTransactionIngestSnapshotPublisher::new(&settings, &lifecycle);
|
let (snapshots, snapshot_source) = crate::RawTransactionIngestSnapshotPublisher::new(&settings, &lifecycle, source_total);
|
||||||
let handle = crate::RawTransactionIngestHandle { snapshots: snapshot_source, stop_sender, stop_token };
|
let handle = crate::RawTransactionIngestHandle { snapshots: snapshot_source, stop_sender, stop_token };
|
||||||
std::mem::drop(runtime.spawn(run_supervisor(settings, lifecycle, port, stop_receiver, snapshots, processing_frontier_receiver, source_spawner)));
|
std::mem::drop(runtime.spawn(run_supervisor(settings, lifecycle, port, stop_receiver, snapshots, processing_frontier_receiver, source_spawner)));
|
||||||
return std::result::Result::Ok(handle);
|
return std::result::Result::Ok(handle);
|
||||||
@@ -505,6 +522,7 @@ async fn supervise_until_stop(
|
|||||||
children: &mut SourceTasks,
|
children: &mut SourceTasks,
|
||||||
admission: &mut crate::RawTransactionAdmission,
|
admission: &mut crate::RawTransactionAdmission,
|
||||||
persistence: &mut PersistenceTasks,
|
persistence: &mut PersistenceTasks,
|
||||||
|
persistence_convergence: &std::sync::Arc<crate::RawTransactionIngestPersistenceConvergence>,
|
||||||
port: &std::option::Option<PersistencePort>,
|
port: &std::option::Option<PersistencePort>,
|
||||||
snapshots: &mut crate::RawTransactionIngestSnapshotPublisher,
|
snapshots: &mut crate::RawTransactionIngestSnapshotPublisher,
|
||||||
processing_frontier_receiver: &mut std::option::Option<ProcessingFrontierReceiver>,
|
processing_frontier_receiver: &mut std::option::Option<ProcessingFrontierReceiver>,
|
||||||
@@ -576,7 +594,7 @@ async fn supervise_until_stop(
|
|||||||
match received {
|
match received {
|
||||||
std::result::Result::Ok(std::option::Option::Some(acquisition)) => {
|
std::result::Result::Ok(std::option::Option::Some(acquisition)) => {
|
||||||
let backpressure_wait_observed = admission.take_backpressure_wait_observed();
|
let backpressure_wait_observed = admission.take_backpressure_wait_observed();
|
||||||
let spawned = spawn_persistence(persistence, port, acquisition);
|
let spawned = spawn_persistence(persistence, persistence_convergence, port, acquisition);
|
||||||
let published = snapshots.record_admission_success(
|
let published = snapshots.record_admission_success(
|
||||||
lifecycle.state(),
|
lifecycle.state(),
|
||||||
admission.queue_depth(),
|
admission.queue_depth(),
|
||||||
@@ -629,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<()> {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -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: 4
|
// 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,13 +20,275 @@ 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>,
|
||||||
source_state: std::option::Option<crate::RawTransactionIngestSourceState>,
|
source_state: std::option::Option<crate::RawTransactionIngestSourceState>,
|
||||||
|
source_total: usize,
|
||||||
|
source_active: usize,
|
||||||
|
source_reconnecting: usize,
|
||||||
|
source_failed: usize,
|
||||||
source_reconnect_total: u64,
|
source_reconnect_total: u64,
|
||||||
source_replay_attempt_total: u64,
|
source_replay_attempt_total: u64,
|
||||||
source_continuity_gap_total: u64,
|
source_continuity_gap_total: u64,
|
||||||
@@ -34,12 +296,22 @@ 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,
|
||||||
source_state: std::option::Option::None,
|
source_state: std::option::Option::None,
|
||||||
|
source_total: 0,
|
||||||
|
source_active: 0,
|
||||||
|
source_reconnecting: 0,
|
||||||
|
source_failed: 0,
|
||||||
source_reconnect_total: 0,
|
source_reconnect_total: 0,
|
||||||
source_replay_attempt_total: 0,
|
source_replay_attempt_total: 0,
|
||||||
source_continuity_gap_total: 0,
|
source_continuity_gap_total: 0,
|
||||||
@@ -47,16 +319,22 @@ 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,
|
||||||
source_state: std::option::Option::None,
|
source_state: std::option::Option::None,
|
||||||
|
source_total: 0,
|
||||||
|
source_active: 0,
|
||||||
|
source_reconnecting: 0,
|
||||||
|
source_failed: 0,
|
||||||
source_reconnect_total: 0,
|
source_reconnect_total: 0,
|
||||||
source_replay_attempt_total: 0,
|
source_replay_attempt_total: 0,
|
||||||
source_continuity_gap_total: 0,
|
source_continuity_gap_total: 0,
|
||||||
@@ -79,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,
|
||||||
@@ -93,6 +371,91 @@ impl crate::RawTransactionIngestProcessingFrontierProjection {
|
|||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a copy carrying source-neutral multi-source lifecycle counts.
|
||||||
|
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_active = source_active;
|
||||||
|
self.source_reconnecting = source_reconnecting;
|
||||||
|
self.source_failed = source_failed;
|
||||||
|
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.
|
||||||
|
pub(crate) const fn source_total(&self) -> usize {
|
||||||
|
return self.source_total;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of sources currently projected Active.
|
||||||
|
pub(crate) const fn source_active(&self) -> usize {
|
||||||
|
return self.source_active;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of sources currently projected Reconnecting.
|
||||||
|
pub(crate) const fn source_reconnecting(&self) -> usize {
|
||||||
|
return self.source_reconnecting;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the number of sources currently projected Failed.
|
||||||
|
pub(crate) const fn source_failed(&self) -> usize {
|
||||||
|
return self.source_failed;
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the latest source-neutral lifecycle state carried by this private projection.
|
/// Returns the latest source-neutral lifecycle state carried by this private projection.
|
||||||
pub(crate) const fn source_state(&self) -> std::option::Option<crate::RawTransactionIngestSourceState> {
|
pub(crate) const fn source_state(&self) -> std::option::Option<crate::RawTransactionIngestSourceState> {
|
||||||
return self.source_state;
|
return self.source_state;
|
||||||
@@ -118,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,
|
||||||
@@ -138,6 +517,10 @@ pub struct RawTransactionIngestSnapshot {
|
|||||||
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>,
|
||||||
source_state: std::option::Option<crate::RawTransactionIngestSourceState>,
|
source_state: std::option::Option<crate::RawTransactionIngestSourceState>,
|
||||||
|
source_total: usize,
|
||||||
|
source_active: usize,
|
||||||
|
source_reconnecting: usize,
|
||||||
|
source_failed: usize,
|
||||||
source_reconnect_total: u64,
|
source_reconnect_total: u64,
|
||||||
source_replay_attempt_total: u64,
|
source_replay_attempt_total: u64,
|
||||||
source_continuity_gap_total: u64,
|
source_continuity_gap_total: u64,
|
||||||
@@ -264,6 +647,98 @@ 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.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn source_total(&self) -> usize {
|
||||||
|
return self.source_total;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the latest number of sources projected Active.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn source_active(&self) -> usize {
|
||||||
|
return self.source_active;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the latest number of sources projected Reconnecting.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn source_reconnecting(&self) -> usize {
|
||||||
|
return self.source_reconnecting;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the latest number of sources projected Failed.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn source_failed(&self) -> usize {
|
||||||
|
return self.source_failed;
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the latest source-neutral lifecycle state when the productive source has started.
|
/// Returns the latest source-neutral lifecycle state when the productive source has started.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub const fn source_state(&self) -> std::option::Option<crate::RawTransactionIngestSourceState> {
|
pub const fn source_state(&self) -> std::option::Option<crate::RawTransactionIngestSourceState> {
|
||||||
@@ -294,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)
|
||||||
@@ -314,6 +800,10 @@ impl std::fmt::Debug for crate::RawTransactionIngestSnapshot {
|
|||||||
.field("processing_frontier_slot", &self.processing_frontier_slot)
|
.field("processing_frontier_slot", &self.processing_frontier_slot)
|
||||||
.field("oldest_pending_slot", &self.oldest_pending_slot)
|
.field("oldest_pending_slot", &self.oldest_pending_slot)
|
||||||
.field("source_state", &self.source_state)
|
.field("source_state", &self.source_state)
|
||||||
|
.field("source_total", &self.source_total)
|
||||||
|
.field("source_active", &self.source_active)
|
||||||
|
.field("source_reconnecting", &self.source_reconnecting)
|
||||||
|
.field("source_failed", &self.source_failed)
|
||||||
.field("source_reconnect_total", &self.source_reconnect_total)
|
.field("source_reconnect_total", &self.source_reconnect_total)
|
||||||
.field("source_replay_attempt_total", &self.source_replay_attempt_total)
|
.field("source_replay_attempt_total", &self.source_replay_attempt_total)
|
||||||
.field("source_continuity_gap_total", &self.source_continuity_gap_total)
|
.field("source_continuity_gap_total", &self.source_continuity_gap_total)
|
||||||
@@ -403,6 +893,7 @@ impl crate::RawTransactionIngestSnapshotPublisher {
|
|||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
settings: &crate::RawTransactionIngestSettings,
|
settings: &crate::RawTransactionIngestSettings,
|
||||||
lifecycle: &ksp_worker_api::WorkerLifecycle,
|
lifecycle: &ksp_worker_api::WorkerLifecycle,
|
||||||
|
source_total: usize,
|
||||||
) -> (crate::RawTransactionIngestSnapshotPublisher, crate::RawTransactionIngestSnapshotSource) {
|
) -> (crate::RawTransactionIngestSnapshotPublisher, crate::RawTransactionIngestSnapshotSource) {
|
||||||
let worker = ksp_worker_api::WorkerSnapshot::new(
|
let worker = ksp_worker_api::WorkerSnapshot::new(
|
||||||
lifecycle.id().clone(),
|
lifecycle.id().clone(),
|
||||||
@@ -414,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(),
|
||||||
@@ -434,6 +941,10 @@ impl crate::RawTransactionIngestSnapshotPublisher {
|
|||||||
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,
|
||||||
source_state: std::option::Option::None,
|
source_state: std::option::Option::None,
|
||||||
|
source_total,
|
||||||
|
source_active: 0,
|
||||||
|
source_reconnecting: 0,
|
||||||
|
source_failed: 0,
|
||||||
source_reconnect_total: 0,
|
source_reconnect_total: 0,
|
||||||
source_replay_attempt_total: 0,
|
source_replay_attempt_total: 0,
|
||||||
source_continuity_gap_total: 0,
|
source_continuity_gap_total: 0,
|
||||||
@@ -449,7 +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(state, self.snapshot.worker.health()),
|
health_for_state(state, self.snapshot.worker.health(), &self.snapshot),
|
||||||
ksp_worker_api::WorkerActivity::Idle,
|
ksp_worker_api::WorkerActivity::Idle,
|
||||||
);
|
);
|
||||||
self.snapshot.worker = worker;
|
self.snapshot.worker = worker;
|
||||||
@@ -526,10 +1037,33 @@ 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();
|
||||||
self.snapshot.source_state = projection.source_state();
|
self.snapshot.source_state = projection.source_state();
|
||||||
|
if projection.source_total() > 0 {
|
||||||
|
self.snapshot.source_total = projection.source_total();
|
||||||
|
self.snapshot.source_active = projection.source_active();
|
||||||
|
self.snapshot.source_reconnecting = projection.source_reconnecting();
|
||||||
|
self.snapshot.source_failed = projection.source_failed();
|
||||||
|
}
|
||||||
self.snapshot.source_reconnect_total = projection.source_reconnect_total();
|
self.snapshot.source_reconnect_total = projection.source_reconnect_total();
|
||||||
self.snapshot.source_replay_attempt_total = projection.source_replay_attempt_total();
|
self.snapshot.source_replay_attempt_total = projection.source_replay_attempt_total();
|
||||||
self.snapshot.source_continuity_gap_total = projection.source_continuity_gap_total();
|
self.snapshot.source_continuity_gap_total = projection.source_continuity_gap_total();
|
||||||
@@ -646,7 +1180,7 @@ impl crate::RawTransactionIngestSnapshotPublisher {
|
|||||||
self.snapshot.worker.kind().clone(),
|
self.snapshot.worker.kind().clone(),
|
||||||
sequence,
|
sequence,
|
||||||
state,
|
state,
|
||||||
health_for_state(state, self.snapshot.worker.health()),
|
health_for_state(state, self.snapshot.worker.health(), &self.snapshot),
|
||||||
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;
|
||||||
@@ -698,8 +1232,41 @@ fn checked_optional_counter(current: u64, increment: bool, field: &'static str)
|
|||||||
return checked_counter(current, field);
|
return checked_counter(current, field);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn health_for_state(state: ksp_worker_api::WorkerState, previous: ksp_worker_api::WorkerHealth) -> ksp_worker_api::WorkerHealth {
|
fn health_for_state(
|
||||||
|
state: ksp_worker_api::WorkerState,
|
||||||
|
previous: ksp_worker_api::WorkerHealth,
|
||||||
|
snapshot: &crate::RawTransactionIngestSnapshot,
|
||||||
|
) -> 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 snapshot.source_failed > 0 => ksp_worker_api::WorkerHealth::Unhealthy,
|
||||||
|
ksp_worker_api::WorkerState::Running if snapshot.source_reconnecting > 0 => 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,
|
||||||
|
|||||||
@@ -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: 1
|
// version: 3
|
||||||
|
|
||||||
//! Cross-layer completeness and security canaries for `0.3.12-pre.010`.
|
//! 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 = "";
|
||||||
@@ -190,3 +190,300 @@ fn v0_3_12_pre_010_public_roots_keep_implementation_modules_private() {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_012_all_five_live_source_families_share_one_worker_common_raw_store_pipeline() {
|
||||||
|
let transport_root = include_str!("../../ksp-onchain-transport-lib/src/lib.rs");
|
||||||
|
let transport_http_transactions = include_str!("../../ksp-onchain-transport-lib/src/rpc_transactions.rs");
|
||||||
|
let transport_http_blocks = include_str!("../../ksp-onchain-transport-lib/src/rpc_blocks.rs");
|
||||||
|
let transport_ws_protocol = include_str!("../../ksp-onchain-transport-lib/src/ws_protocol_session.rs");
|
||||||
|
let transport_grpc_stream = include_str!("../../ksp-onchain-transport-lib/src/grpc_stream.rs");
|
||||||
|
let worker_resources = include_str!("../src/runtime_resources.rs");
|
||||||
|
let worker_persistence = include_str!("../src/persistence.rs");
|
||||||
|
let common_raw_root = include_str!("../../ksp-raw-transaction-lib/src/lib.rs");
|
||||||
|
let store_root = include_str!("../../ksp-store-lib/src/lib.rs");
|
||||||
|
let store = include_str!("../../ksp-store-lib/src/store.rs");
|
||||||
|
for required in [
|
||||||
|
"HeliusTransaction(crate::RawTransactionIngestHeliusTransactionSource)",
|
||||||
|
"HttpBlockPolling(crate::RawTransactionIngestHttpBlockPollingSource)",
|
||||||
|
"StandardBlock(crate::RawTransactionIngestStandardBlockSource)",
|
||||||
|
"StandardLogs(crate::RawTransactionIngestStandardLogsSource)",
|
||||||
|
"Yellowstone(crate::RawTransactionIngestYellowstoneSource)",
|
||||||
|
"RawTransactionIngestGlobalHydrationRegistry",
|
||||||
|
"run_live_sources",
|
||||||
|
"RawTransactionIngress",
|
||||||
|
] {
|
||||||
|
assert!(worker_resources.contains(required), "pre.012 Worker source/pipeline contract missing: {required}");
|
||||||
|
}
|
||||||
|
for required in ["persist_raw_transaction_ingest_converged_acquisition", "RawTransactionIngestPersistenceConvergence", "record_observation"] {
|
||||||
|
assert!(worker_persistence.contains(required), "pre.012 Worker persistence contract missing: {required}");
|
||||||
|
}
|
||||||
|
for required in ["SolanaStandardWsSession", "HeliusLaserStreamWsSession"] {
|
||||||
|
assert!(transport_ws_protocol.contains(required), "pre.012 WS Transport facade missing: {required}");
|
||||||
|
}
|
||||||
|
assert!(transport_grpc_stream.contains("pub struct SolanaYellowstoneGrpcSubscribeSession"));
|
||||||
|
assert!(transport_http_transactions.contains("pub async fn get_transaction_observed("));
|
||||||
|
assert!(transport_http_blocks.contains("pub async fn get_block_observed("));
|
||||||
|
assert!(transport_http_blocks.contains("pub async fn get_blocks_with_limit("));
|
||||||
|
for required in ["RawTransactionAcquisition", "RawSolanaMessageVersion", "RawSolanaTransactionWire"] {
|
||||||
|
assert!(common_raw_root.contains(required), "pre.012 Common RAW contract missing: {required}");
|
||||||
|
}
|
||||||
|
for required in ["RawTransactionWrite", "RawTransactionObservationWrite", "RawPayload"] {
|
||||||
|
assert!(store_root.contains(required), "pre.012 Store facade contract missing: {required}");
|
||||||
|
}
|
||||||
|
for required in ["persist_raw_transaction_acquisition", "record_raw_transaction_observation"] {
|
||||||
|
assert!(store.contains(required), "pre.012 Store dispatch contract missing: {required}");
|
||||||
|
}
|
||||||
|
assert!(!transport_root.contains("ksp_raw_transaction_lib"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_012_legacy_v0_v1_is_proven_from_transport_through_worker_common_raw_to_store() {
|
||||||
|
let transport_grpc = include_str!("../../ksp-onchain-transport-lib/unit_tests/grpc_subscribe.rs");
|
||||||
|
let transport_blocks = include_str!("../../ksp-onchain-transport-lib/unit_tests/rpc_blocks.rs");
|
||||||
|
let worker = include_str!("../unit_tests/runtime_resources.rs");
|
||||||
|
let worker_resources = include_str!("../src/runtime_resources.rs");
|
||||||
|
let worker_persistence = include_str!("../src/persistence.rs");
|
||||||
|
let common_raw_wire = include_str!("../../ksp-raw-transaction-lib/unit_tests/wire.rs");
|
||||||
|
let common_raw_canonical = include_str!("../../ksp-raw-transaction-lib/unit_tests/canonical.rs");
|
||||||
|
let store_api = include_str!("../../ksp-store-api/tests/public_api.rs");
|
||||||
|
let store = include_str!("../../ksp-store-lib/src/store.rs");
|
||||||
|
assert!(transport_grpc.contains("yellowstone_transaction_update_decodes_current_storage_wire_including_v1_config_and_meta"));
|
||||||
|
assert!(transport_blocks.contains("typed_get_block_modern_full_config_preserves_rich_wire_and_simd_fields"));
|
||||||
|
for required in [
|
||||||
|
"v0_3_13_pre_004_standard_block_qualifies_legacy_v0_v1_and_rejects_ambiguous_or_future_versions",
|
||||||
|
"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",
|
||||||
|
"RawSolanaMessageVersion::Legacy",
|
||||||
|
"RawSolanaMessageVersion::V0",
|
||||||
|
"RawSolanaMessageVersion::V1",
|
||||||
|
] {
|
||||||
|
assert!(worker.contains(required), "pre.012 Worker Legacy/V0/V1 proof missing: {required}");
|
||||||
|
}
|
||||||
|
assert!(worker_resources.contains("SolanaGetTransactionConfig::new"));
|
||||||
|
assert!(worker_resources.contains("std::option::Option::Some(1),"));
|
||||||
|
for required in [
|
||||||
|
"pre_006_legacy_v0_and_v1_wire_goldens_are_exact",
|
||||||
|
"pre_006_v1_structural_guards_reject_alt_duplicates_and_missing_config",
|
||||||
|
"RawSolanaMessageVersion::V1",
|
||||||
|
] {
|
||||||
|
assert!(common_raw_wire.contains(required), "pre.012 Common RAW V1 proof missing: {required}");
|
||||||
|
}
|
||||||
|
assert!(common_raw_canonical.contains("pre_002_canonical_raw_v1_golden_bytes_and_hash_are_exact"));
|
||||||
|
for required in ["block_time", "content_hash", "format_id", "format_version", "slot"] {
|
||||||
|
assert!(worker_persistence.contains(required), "pre.012 canonical Store identity field missing: {required}");
|
||||||
|
}
|
||||||
|
assert!(store_api.contains("public_pre_003_raw_transaction_and_observation_are_constructible_from_crate_root"));
|
||||||
|
assert!(store_api.contains("RawPayload::try_new(format, 1"));
|
||||||
|
assert!(store.contains("persist_raw_transaction_acquisition"));
|
||||||
|
assert!(store.contains("record_raw_transaction_observation"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_012_yellowstone_non_regression_covers_identity_v1_reconnect_gap_and_worker_hydration() {
|
||||||
|
let transport_subscribe = include_str!("../../ksp-onchain-transport-lib/unit_tests/grpc_subscribe.rs");
|
||||||
|
let transport_stream = include_str!("../../ksp-onchain-transport-lib/unit_tests/grpc_stream.rs");
|
||||||
|
let transport_public = include_str!("../../ksp-onchain-transport-lib/tests/public_api.rs");
|
||||||
|
let worker = include_str!("../unit_tests/runtime_resources.rs");
|
||||||
|
let worker_hardening = include_str!("hardening.rs");
|
||||||
|
for required in [
|
||||||
|
"yellowstone_subscribe_request_identity_is_order_stable_exact_and_debug_redacted",
|
||||||
|
"yellowstone_transaction_update_decodes_current_storage_wire_including_v1_config_and_meta",
|
||||||
|
"yellowstone_transaction_status_update_preserves_error_and_rejects_malformed_signature",
|
||||||
|
"yellowstone_block_update_reuses_transaction_account_entry_dtos_and_preserves_server_counts",
|
||||||
|
] {
|
||||||
|
assert!(transport_subscribe.contains(required), "pre.012 Yellowstone Transport subscribe proof missing: {required}");
|
||||||
|
}
|
||||||
|
for required in [
|
||||||
|
"yellowstone_reconnect_replays_from_last_observed_slot_and_counts_duplicate_identity",
|
||||||
|
"yellowstone_replay_info_proves_and_clamps_retention_gap_without_lossless_claim",
|
||||||
|
"yellowstone_shutdown_interrupts_reconnect_backoff_and_mutation_is_rejected_during_reconnect",
|
||||||
|
] {
|
||||||
|
assert!(transport_stream.contains(required), "pre.012 Yellowstone Transport lifecycle proof missing: {required}");
|
||||||
|
}
|
||||||
|
assert!(transport_public.contains("public_v0_3_13_pre_002_yellowstone_subscribe_identity_is_opaque_and_available_from_crate_root"));
|
||||||
|
for required in [
|
||||||
|
"pre_002_source_accepts_matching_confirmed_transaction_and_get_transaction_route_without_io",
|
||||||
|
"v0_3_13_pre_002_live_source_key_is_stable_and_request_sensitive",
|
||||||
|
"pre_003_transaction_signal_fixture_preserves_exact_source_neutral_identity",
|
||||||
|
"pre_005_block_projects_transaction_signals_in_exact_source_order",
|
||||||
|
"pre_005_block_meta_and_slot_project_continuity_only_without_remote_dead_error",
|
||||||
|
"pre_004_observed_get_transaction_closes_signal_to_common_raw_ingress_with_composite_provenance",
|
||||||
|
"pre_008_reconnect_replay_and_proven_gap_are_distinct_monotone_and_frontier_preserving",
|
||||||
|
"v0_3_13_pre_008_global_hydration_registry_coalesces_cross_source_key_to_one_leader",
|
||||||
|
] {
|
||||||
|
assert!(worker.contains(required), "pre.012 Yellowstone Worker non-regression proof missing: {required}");
|
||||||
|
}
|
||||||
|
for required in [
|
||||||
|
"v0_3_12_pre_003_private_signal_debug_and_shape_do_not_expose_signature_filters_or_payload",
|
||||||
|
"v0_3_12_pre_008_reconnect_projection_is_source_neutral_bounded_and_contains_no_replay_material",
|
||||||
|
] {
|
||||||
|
assert!(worker_hardening.contains(required), "pre.012 Yellowstone Worker hardening proof missing: {required}");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_012_security_redaction_matrix_covers_all_live_sources_and_lower_layers() {
|
||||||
|
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 transport_ws = include_str!("../../ksp-onchain-transport-lib/unit_tests/ws_helius_transactions.rs");
|
||||||
|
let worker = include_str!("hardening.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");
|
||||||
|
for required in [
|
||||||
|
"yellowstone_subscribe_debug_omits_filter_names_and_future_payloads",
|
||||||
|
"yellowstone_transaction_filters_encode_complete_current_wire_and_redact_selectors",
|
||||||
|
] {
|
||||||
|
assert!(transport_subscribe.contains(required), "pre.012 Transport gRPC redaction proof missing: {required}");
|
||||||
|
}
|
||||||
|
assert!(transport_unary.contains("yellowstone_remote_status_does_not_copy_message_details_or_metadata"));
|
||||||
|
assert!(transport_ws.contains("helius_transaction_notification_debug_omits_raw_provider_payloads"));
|
||||||
|
for required in [
|
||||||
|
"v0_3_12_pre_003_private_signal_debug_and_shape_do_not_expose_signature_filters_or_payload",
|
||||||
|
"v0_3_13_pre_003_standard_logs_redaction_and_reference_only_contract_are_explicit",
|
||||||
|
"v0_3_13_pre_004_standard_block_redaction_version_and_null_guards_are_explicit",
|
||||||
|
"v0_3_13_pre_005_helius_transaction_redaction_full_reference_and_tier_neutrality_are_explicit",
|
||||||
|
"v0_3_13_pre_006_http_block_polling_is_bounded_run_local_stop_preemptible_and_redacted",
|
||||||
|
"v0_3_13_pre_007_multi_source_supervisor_is_fail_closed_joined_and_does_not_publish_source_identity",
|
||||||
|
"v0_3_13_pre_008_cross_source_convergence_is_bounded_conflict_checked_and_private",
|
||||||
|
"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_011_shutdown_races_are_bounded_joined_atomic_and_counter_safe",
|
||||||
|
] {
|
||||||
|
assert!(worker.contains(required), "pre.012 Worker security proof missing: {required}");
|
||||||
|
}
|
||||||
|
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_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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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: 16
|
// version: 31
|
||||||
|
|
||||||
//! Dependency firewall canaries for the RAW transaction ingest Worker foundation.
|
//! Dependency firewall canaries for the RAW transaction ingest Worker foundation.
|
||||||
|
|
||||||
@@ -85,7 +85,6 @@ fn v0_3_12_pre_003_transaction_and_status_adapters_are_private_and_transport_fac
|
|||||||
"pub struct RawTransactionIngestSourceSignal",
|
"pub struct RawTransactionIngestSourceSignal",
|
||||||
"pub(crate) struct RawTransactionIngestSourceSignal",
|
"pub(crate) struct RawTransactionIngestSourceSignal",
|
||||||
"pub use self::runtime_resources::RawTransactionIngestSourceSignal",
|
"pub use self::runtime_resources::RawTransactionIngestSourceSignal",
|
||||||
"get_block_observed",
|
|
||||||
"ksp_store_postgres_lib::",
|
"ksp_store_postgres_lib::",
|
||||||
"reqwest::",
|
"reqwest::",
|
||||||
"tonic::",
|
"tonic::",
|
||||||
@@ -125,17 +124,9 @@ fn v0_3_12_pre_004_hydration_contract_uses_only_transport_facade_common_raw_and_
|
|||||||
] {
|
] {
|
||||||
assert!(resources.contains(required), "required pre.004 hydration contract missing: {required}");
|
assert!(resources.contains(required), "required pre.004 hydration contract missing: {required}");
|
||||||
}
|
}
|
||||||
assert!(resources.contains("fn finalize_yellowstone_hydration"));
|
assert!(resources.contains("fn finalize_hydration"));
|
||||||
assert!(resources.contains("fn fetch_yellowstone_hydration"));
|
assert!(resources.contains("fn fetch_hydration"));
|
||||||
for forbidden in [
|
for forbidden in ["ksp_config_lib::", "ksp_job_backfill_lib::", "ksp_store_postgres_lib::", "reqwest::", "tonic::", "yellowstone_grpc_proto::"] {
|
||||||
"get_block_observed",
|
|
||||||
"ksp_config_lib::",
|
|
||||||
"ksp_job_backfill_lib::",
|
|
||||||
"ksp_store_postgres_lib::",
|
|
||||||
"reqwest::",
|
|
||||||
"tonic::",
|
|
||||||
"yellowstone_grpc_proto::",
|
|
||||||
] {
|
|
||||||
assert!(!resources.contains(forbidden) && !root.contains(forbidden), "pre.004 crossed a forbidden boundary: {forbidden}");
|
assert!(!resources.contains(forbidden) && !root.contains(forbidden), "pre.004 crossed a forbidden boundary: {forbidden}");
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -157,15 +148,7 @@ fn v0_3_12_pre_005_block_and_continuity_adapters_remain_private_and_transport_fa
|
|||||||
] {
|
] {
|
||||||
assert!(resources.contains(required), "required pre.005 private adapter contract missing: {required}");
|
assert!(resources.contains(required), "required pre.005 private adapter contract missing: {required}");
|
||||||
}
|
}
|
||||||
for forbidden in [
|
for forbidden in ["ksp_config_lib::", "ksp_job_backfill_lib::", "ksp_store_postgres_lib::", "reqwest::", "tonic::", "yellowstone_grpc_proto::"] {
|
||||||
"get_block_observed",
|
|
||||||
"ksp_config_lib::",
|
|
||||||
"ksp_job_backfill_lib::",
|
|
||||||
"ksp_store_postgres_lib::",
|
|
||||||
"reqwest::",
|
|
||||||
"tonic::",
|
|
||||||
"yellowstone_grpc_proto::",
|
|
||||||
] {
|
|
||||||
assert!(!resources.contains(forbidden) && !root.contains(forbidden), "pre.005 crossed a forbidden boundary: {forbidden}");
|
assert!(!resources.contains(forbidden) && !root.contains(forbidden), "pre.005 crossed a forbidden boundary: {forbidden}");
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -175,11 +158,7 @@ fn v0_3_12_pre_005_block_and_continuity_adapters_remain_private_and_transport_fa
|
|||||||
fn v0_3_12_pre_006_productive_source_uses_transport_session_bounded_coalescence_and_existing_admission() {
|
fn v0_3_12_pre_006_productive_source_uses_transport_session_bounded_coalescence_and_existing_admission() {
|
||||||
let runtime = include_str!("../src/runtime.rs");
|
let runtime = include_str!("../src/runtime.rs");
|
||||||
let resources = include_str!("../src/runtime_resources.rs");
|
let resources = include_str!("../src/runtime_resources.rs");
|
||||||
for required in [
|
for required in ["children.spawn", ".run_live_sources(source_settings, stop_receiver, admission_sender, processing_frontier_sender)"] {
|
||||||
"runtime_resources.into_yellowstone_source()",
|
|
||||||
"children.spawn",
|
|
||||||
"source.run(source_settings, stop_receiver, admission_sender, processing_frontier_sender)",
|
|
||||||
] {
|
|
||||||
assert!(runtime.contains(required), "required pre.006 supervisor wiring missing: {required}");
|
assert!(runtime.contains(required), "required pre.006 supervisor wiring missing: {required}");
|
||||||
}
|
}
|
||||||
for required in [
|
for required in [
|
||||||
@@ -188,19 +167,20 @@ fn v0_3_12_pre_006_productive_source_uses_transport_session_bounded_coalescence_
|
|||||||
"RawTransactionIngestHydrationCoordinator",
|
"RawTransactionIngestHydrationCoordinator",
|
||||||
"std::collections::BTreeMap",
|
"std::collections::BTreeMap",
|
||||||
"tokio::task::JoinSet",
|
"tokio::task::JoinSet",
|
||||||
"MAX_RAW_TRANSACTION_INGEST_ADMISSION_QUEUE_CAPACITY",
|
"RawTransactionIngestHydrationCoordinator::with_global_registry(",
|
||||||
|
"hydration_pending_limit,",
|
||||||
|
"hydration_in_flight_limit,",
|
||||||
"get_transaction_observed",
|
"get_transaction_observed",
|
||||||
"admission_sender.send(ingress)",
|
"admission_sender.send(ingress)",
|
||||||
"tasks.abort_all()",
|
"tasks.abort_all()",
|
||||||
"session.close().await",
|
"session.close().await",
|
||||||
"commitment: commitment.as_str()",
|
"commitment: hydration.commitment.as_str()",
|
||||||
"network: signal.network.clone()",
|
"network: signal.network.clone()",
|
||||||
"signature: signal.signature",
|
"signature: signal.signature",
|
||||||
] {
|
] {
|
||||||
assert!(resources.contains(required), "required pre.006 productive-source contract missing: {required}");
|
assert!(resources.contains(required), "required pre.006 productive-source contract missing: {required}");
|
||||||
}
|
}
|
||||||
for forbidden in [
|
for forbidden in [
|
||||||
"get_block_observed",
|
|
||||||
"ksp_config_lib::",
|
"ksp_config_lib::",
|
||||||
"ksp_job_backfill_lib::",
|
"ksp_job_backfill_lib::",
|
||||||
"ksp_store_postgres_lib::",
|
"ksp_store_postgres_lib::",
|
||||||
@@ -214,6 +194,175 @@ fn v0_3_12_pre_006_productive_source_uses_transport_session_bounded_coalescence_
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_006_http_block_polling_reuses_transport_facades_without_becoming_backfill() {
|
||||||
|
let root = include_str!("../src/lib.rs");
|
||||||
|
let resources = include_str!("../src/runtime_resources.rs");
|
||||||
|
for required in [
|
||||||
|
"RawTransactionIngestHttpBlockPollingSource",
|
||||||
|
"get_slot(&self.polling_role",
|
||||||
|
"get_blocks_with_limit(",
|
||||||
|
"get_block_observed(&self.polling_role",
|
||||||
|
"let mut next_scan_slot = start_slot",
|
||||||
|
"next_scan_slot = slot",
|
||||||
|
"tokio::time::sleep(self.poll_interval)",
|
||||||
|
"RawAcquisitionOrigin::Live",
|
||||||
|
"block_polling_get_block",
|
||||||
|
"SolanaTransactionEncoding::Base64",
|
||||||
|
"SolanaTransactionDetails::Full",
|
||||||
|
"std::option::Option::Some(1)",
|
||||||
|
] {
|
||||||
|
assert!(resources.contains(required), "required pre.006 HTTP polling contract missing: {required}");
|
||||||
|
}
|
||||||
|
let source_impl = match resources.split_once("impl crate::RawTransactionIngestHttpBlockPollingSource {") {
|
||||||
|
std::option::Option::Some((_, tail)) => match tail.split_once("impl std::fmt::Debug for crate::RawTransactionIngestHttpBlockPollingSource") {
|
||||||
|
std::option::Option::Some((value, _)) => value,
|
||||||
|
std::option::Option::None => "",
|
||||||
|
},
|
||||||
|
std::option::Option::None => "",
|
||||||
|
};
|
||||||
|
assert_eq!(source_impl.matches("get_block_observed(").count(), 1);
|
||||||
|
for forbidden in [
|
||||||
|
"ksp_config_lib::",
|
||||||
|
"ksp_job_backfill_lib::",
|
||||||
|
"ksp_store_postgres_lib::",
|
||||||
|
"reqwest::",
|
||||||
|
"tokio_tungstenite::",
|
||||||
|
"tonic::",
|
||||||
|
"yellowstone_grpc_proto::",
|
||||||
|
"unbounded_channel",
|
||||||
|
] {
|
||||||
|
assert!(!source_impl.contains(forbidden) && !root.contains(forbidden), "pre.006 HTTP polling crossed a forbidden boundary: {forbidden}");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_003_standard_logs_source_reuses_transport_facades_and_common_hydration_only() {
|
||||||
|
let root = include_str!("../src/lib.rs");
|
||||||
|
let runtime = include_str!("../src/runtime.rs");
|
||||||
|
let resources = include_str!("../src/runtime_resources.rs");
|
||||||
|
for required in [
|
||||||
|
"RawTransactionIngestStandardLogsSource",
|
||||||
|
"SolanaStandardWsSession::connect",
|
||||||
|
".logs_subscribe(",
|
||||||
|
"SolanaLogsSubscribeFilter",
|
||||||
|
"SolanaCommitmentConfig::new",
|
||||||
|
"project_standard_logs_signal",
|
||||||
|
"RawTransactionIngestSourceFamily::Logs",
|
||||||
|
"logs_get_transaction",
|
||||||
|
"solana_ws_http",
|
||||||
|
"get_transaction_observed",
|
||||||
|
"RawTransactionIngestHydrationCoordinator::with_global_registry(",
|
||||||
|
"hydration_pending_limit,",
|
||||||
|
"hydration_in_flight_limit,",
|
||||||
|
"std::option::Option::Some(1)",
|
||||||
|
] {
|
||||||
|
assert!(resources.contains(required), "required pre.003 standard logs contract missing: {required}");
|
||||||
|
}
|
||||||
|
assert!(runtime.contains("run_live_sources"));
|
||||||
|
for forbidden in [
|
||||||
|
"ksp_config_lib::",
|
||||||
|
"ksp_job_backfill_lib::",
|
||||||
|
"ksp_store_postgres_lib::",
|
||||||
|
"reqwest::",
|
||||||
|
"tokio_tungstenite::",
|
||||||
|
"tonic::",
|
||||||
|
"yellowstone_grpc_proto::",
|
||||||
|
"unbounded_channel",
|
||||||
|
] {
|
||||||
|
assert!(
|
||||||
|
!runtime.contains(forbidden) && !resources.contains(forbidden) && !root.contains(forbidden),
|
||||||
|
"pre.003 crossed a forbidden boundary: {forbidden}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_004_standard_block_source_is_direct_raw_and_transport_facade_only() {
|
||||||
|
let root = include_str!("../src/lib.rs");
|
||||||
|
let resources = include_str!("../src/runtime_resources.rs");
|
||||||
|
for required in [
|
||||||
|
"RawTransactionIngestStandardBlockSource",
|
||||||
|
"SolanaStandardWsSession::connect",
|
||||||
|
".block_subscribe(",
|
||||||
|
"SolanaBlockSubscribeConfig::new",
|
||||||
|
"SolanaTransactionEncoding::Base64",
|
||||||
|
"SolanaTransactionDetails::Full",
|
||||||
|
"std::option::Option::Some(1)",
|
||||||
|
"std::option::Option::Some(false)",
|
||||||
|
"build_standard_block_material",
|
||||||
|
"RawTransactionMaterial::binary_base64_with_embedded_signature",
|
||||||
|
"RawTransactionVersion::Number(1)",
|
||||||
|
"block_subscribe",
|
||||||
|
"source.standard_block_missing",
|
||||||
|
"source.standard_block_remote_error",
|
||||||
|
] {
|
||||||
|
assert!(resources.contains(required), "required pre.004 standard block contract missing: {required}");
|
||||||
|
}
|
||||||
|
for forbidden in [
|
||||||
|
"ksp_config_lib::",
|
||||||
|
"ksp_job_backfill_lib::",
|
||||||
|
"ksp_store_postgres_lib::",
|
||||||
|
"reqwest::",
|
||||||
|
"tokio_tungstenite::",
|
||||||
|
"tonic::",
|
||||||
|
"yellowstone_grpc_proto::",
|
||||||
|
"unbounded_channel",
|
||||||
|
] {
|
||||||
|
assert!(
|
||||||
|
!runtime_contains(forbidden) && !resources.contains(forbidden) && !root.contains(forbidden),
|
||||||
|
"pre.004 crossed a forbidden standard-block boundary: {forbidden}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_005_helius_transaction_source_reuses_transport_facade_and_common_hydration_only() {
|
||||||
|
let root = include_str!("../src/lib.rs");
|
||||||
|
let runtime = include_str!("../src/runtime.rs");
|
||||||
|
let resources = include_str!("../src/runtime_resources.rs");
|
||||||
|
for required in [
|
||||||
|
"RawTransactionIngestHeliusTransactionSource",
|
||||||
|
"HeliusLaserStreamWsSession::connect",
|
||||||
|
".transaction_subscribe(",
|
||||||
|
"HeliusTransactionSubscribeOptions::new",
|
||||||
|
"HeliusTransactionSubscribeEncoding::Base64",
|
||||||
|
"SolanaTransactionDetails::Full",
|
||||||
|
"std::option::Option::Some(1)",
|
||||||
|
"project_helius_transaction_signal",
|
||||||
|
"helius_ws_http",
|
||||||
|
"get_transaction_observed",
|
||||||
|
"RawTransactionIngestHydrationCoordinator::with_global_registry(",
|
||||||
|
"hydration_pending_limit,",
|
||||||
|
"hydration_in_flight_limit,",
|
||||||
|
] {
|
||||||
|
assert!(resources.contains(required), "required pre.005 Helius transaction contract missing: {required}");
|
||||||
|
}
|
||||||
|
assert_eq!(resources.matches("get_transaction_observed(").count(), 1);
|
||||||
|
for forbidden in [
|
||||||
|
"ksp_config_lib::",
|
||||||
|
"ksp_job_backfill_lib::",
|
||||||
|
"ksp_store_postgres_lib::",
|
||||||
|
"reqwest::",
|
||||||
|
"tokio_tungstenite::",
|
||||||
|
"helius_sdk",
|
||||||
|
"unbounded_channel",
|
||||||
|
] {
|
||||||
|
assert!(
|
||||||
|
!runtime.contains(forbidden) && !resources.contains(forbidden) && !root.contains(forbidden),
|
||||||
|
"pre.005 crossed a forbidden Helius transaction boundary: {forbidden}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn runtime_contains(value: &str) -> bool {
|
||||||
|
return include_str!("../src/runtime.rs").contains(value);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn v0_3_12_pre_006_source_surface_hardens_shutdown_and_faults_without_backend_edges() {
|
fn v0_3_12_pre_006_source_surface_hardens_shutdown_and_faults_without_backend_edges() {
|
||||||
let root = include_str!("../src/lib.rs");
|
let root = include_str!("../src/lib.rs");
|
||||||
@@ -321,6 +470,46 @@ fn manifest_dependency_names(section: &str) -> std::vec::Vec<&str> {
|
|||||||
return names;
|
return names;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_007_multi_source_supervisor_and_inventory_remain_private_bounded_and_source_neutral() {
|
||||||
|
let root = include_str!("../src/lib.rs");
|
||||||
|
let runtime = include_str!("../src/runtime.rs");
|
||||||
|
let resources = include_str!("../src/runtime_resources.rs");
|
||||||
|
for required in [
|
||||||
|
"RawTransactionIngestSourceInventory",
|
||||||
|
"RawTransactionIngestSourceInventoryPublisher",
|
||||||
|
"for (entry_index, source) in self.sources.into_iter().enumerate()",
|
||||||
|
"tokio::task::JoinSet::new()",
|
||||||
|
"supervise_live_source_tasks",
|
||||||
|
"drain_live_source_tasks",
|
||||||
|
"source_stop_sender.send_replace(true)",
|
||||||
|
"processing_frontier_sender.clone()",
|
||||||
|
"source.configured_source_closed",
|
||||||
|
] {
|
||||||
|
assert!(resources.contains(required), "required pre.007 multi-source supervisor contract missing: {required}");
|
||||||
|
}
|
||||||
|
assert!(runtime.contains("run_live_sources"));
|
||||||
|
assert!(!runtime.contains("validate_single_source_activation"));
|
||||||
|
assert!(!resources.contains("multi_source_activation_pending"));
|
||||||
|
for forbidden in [
|
||||||
|
"pub struct RawTransactionIngestSourceInventory",
|
||||||
|
"pub struct RawTransactionIngestSourceInventoryPublisher",
|
||||||
|
"unbounded_channel",
|
||||||
|
"ksp_config_lib::",
|
||||||
|
"ksp_job_backfill_lib::",
|
||||||
|
"ksp_store_postgres_lib::",
|
||||||
|
"reqwest::",
|
||||||
|
"tonic::",
|
||||||
|
"yellowstone_grpc_proto::",
|
||||||
|
] {
|
||||||
|
assert!(
|
||||||
|
!root.contains(forbidden) && !runtime.contains(forbidden) && !resources.contains(forbidden),
|
||||||
|
"pre.007 crossed a private/bounded boundary: {forbidden}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn v0_3_12_pre_007_processing_frontier_remains_run_local_and_backend_neutral_after_continuity_extension() {
|
fn v0_3_12_pre_007_processing_frontier_remains_run_local_and_backend_neutral_after_continuity_extension() {
|
||||||
let runtime = include_str!("../src/runtime.rs");
|
let runtime = include_str!("../src/runtime.rs");
|
||||||
@@ -385,3 +574,220 @@ fn v0_3_12_pre_008_worker_observes_transport_reconnect_replay_and_faults_only_on
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_008_cross_source_convergence_reuses_store_and_transport_facades_only() {
|
||||||
|
let root = include_str!("../src/lib.rs");
|
||||||
|
let persistence = include_str!("../src/persistence.rs");
|
||||||
|
let runtime = include_str!("../src/runtime.rs");
|
||||||
|
let resources = include_str!("../src/runtime_resources.rs");
|
||||||
|
for required in [
|
||||||
|
"RawTransactionIngestPersistenceConvergence",
|
||||||
|
"persist_raw_transaction_ingest_converged_acquisition",
|
||||||
|
"RawTransactionIngestPersistencePort",
|
||||||
|
"record_raw_transaction_observation",
|
||||||
|
"RawTransactionObservationWrite",
|
||||||
|
"payload().content_hash()",
|
||||||
|
"content_conflict_error()",
|
||||||
|
] {
|
||||||
|
assert!(persistence.contains(required) || root.contains(required), "required pre.008 persistence convergence contract missing: {required}");
|
||||||
|
}
|
||||||
|
for required in [
|
||||||
|
"RawTransactionIngestGlobalHydrationRegistry",
|
||||||
|
"fetch_hydration_shared",
|
||||||
|
"subscribe_or_lead",
|
||||||
|
"get_transaction_observed",
|
||||||
|
"settings.admission_queue_capacity()",
|
||||||
|
] {
|
||||||
|
assert!(resources.contains(required), "required pre.008 hydration convergence contract missing: {required}");
|
||||||
|
}
|
||||||
|
assert!(runtime.contains("settings.admission_queue_capacity().max(settings.persistence_concurrency())"));
|
||||||
|
for forbidden in ["ksp_store_postgres_lib::", "reqwest::", "tonic::", "yellowstone_grpc_proto::", "ksp_job_backfill_lib::", "unbounded_channel"] {
|
||||||
|
assert!(
|
||||||
|
!persistence.contains(forbidden) && !runtime.contains(forbidden) && !resources.contains(forbidden),
|
||||||
|
"pre.008 convergence crossed a dependency/boundedness boundary: {forbidden}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_009_global_bounds_and_fairness_stay_inside_worker_facades() {
|
||||||
|
let admission = include_str!("../src/admission.rs");
|
||||||
|
let persistence = include_str!("../src/persistence.rs");
|
||||||
|
let resources = include_str!("../src/runtime_resources.rs");
|
||||||
|
for required in [
|
||||||
|
"validate_hydration_fairness_capacity",
|
||||||
|
"let hydration_pending_budget = settings.admission_queue_capacity();",
|
||||||
|
"let hydration_in_flight_budget = settings.persistence_concurrency();",
|
||||||
|
"hydration_pending_limit",
|
||||||
|
"hydration_in_flight_limit",
|
||||||
|
"tokio::sync::Semaphore",
|
||||||
|
"acquire_owned",
|
||||||
|
"RawTransactionIngestHydrationLeaderGuard",
|
||||||
|
"RawTransactionIngestCanonicalState",
|
||||||
|
"content_conflict_error()",
|
||||||
|
] {
|
||||||
|
assert!(
|
||||||
|
admission.contains(required) || persistence.contains(required) || resources.contains(required),
|
||||||
|
"required pre.009 global-bound contract 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!(
|
||||||
|
!admission.contains(forbidden) && !persistence.contains(forbidden) && !resources.contains(forbidden),
|
||||||
|
"pre.009 crossed a Worker facade boundary: {forbidden}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_010_multi_source_snapshot_health_remains_source_neutral_and_backend_free() {
|
||||||
|
let snapshot = include_str!("../src/snapshot.rs");
|
||||||
|
let resources = include_str!("../src/runtime_resources.rs");
|
||||||
|
for required in ["source_total", "source_active", "source_reconnecting", "source_failed", "WorkerHealth::Degraded", "with_source_counts"] {
|
||||||
|
assert!(snapshot.contains(required) || resources.contains(required), "required pre.010 source-neutral snapshot contract missing: {required}");
|
||||||
|
}
|
||||||
|
for forbidden in ["ksp_store_postgres_lib::", "reqwest::", "tonic::", "yellowstone_grpc_proto::", "SourceSnapshotByKey", "provider_health"] {
|
||||||
|
assert!(!snapshot.contains(forbidden), "pre.010 snapshot crossed source-neutral/backend boundary: {forbidden}");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_011_shutdown_race_hardening_stays_inside_worker_and_existing_facades() {
|
||||||
|
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()",
|
||||||
|
"children.abort_all()",
|
||||||
|
"while persistence.join_next().await.is_some() {}",
|
||||||
|
"while children.join_next().await.is_some() {}",
|
||||||
|
"ERROR_CODE_RAW_TRANSACTION_INGEST_COUNTER_EXHAUSTED",
|
||||||
|
] {
|
||||||
|
assert!(runtime.contains(required), "required pre.011 runtime shutdown guard missing: {required}");
|
||||||
|
}
|
||||||
|
for required in [
|
||||||
|
"RawTransactionIngestHydrationLeaderGuard",
|
||||||
|
"publish_and_remove",
|
||||||
|
"pending.remove(key)",
|
||||||
|
"source.inventory_key_mismatch",
|
||||||
|
"checked_add(projection.hydration_pending())",
|
||||||
|
"checked_add(projection.source_reconnect_total())",
|
||||||
|
] {
|
||||||
|
assert!(resources.contains(required), "required pre.011 source-race 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.011 crossed a Worker/facade boundary: {forbidden}");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_012_cross_layer_closure_does_not_expand_worker_dependency_graph() {
|
||||||
|
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_13_pre_012_all_five_live_source_families_share_one_worker_common_raw_store_pipeline",
|
||||||
|
"v0_3_13_pre_012_legacy_v0_v1_is_proven_from_transport_through_worker_common_raw_to_store",
|
||||||
|
"v0_3_13_pre_012_yellowstone_non_regression_covers_identity_v1_reconnect_gap_and_worker_hydration",
|
||||||
|
"v0_3_13_pre_012_security_redaction_matrix_covers_all_live_sources_and_lower_layers",
|
||||||
|
] {
|
||||||
|
assert!(cross_layer.contains(required), "pre.012 cross-layer closure proof missing: {required}");
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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: 11
|
// version: 37
|
||||||
|
|
||||||
//! External public, security, redaction and release-boundary hardening canaries for `pre.010`.
|
//! 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);
|
||||||
@@ -159,7 +159,15 @@ fn pre_010_source_visibility_contract_uses_crate_root_for_shared_items() {
|
|||||||
(include_str!("../src/runtime.rs"), &["RawTransactionIngestHandle", "RawTransactionIngestWorker"]),
|
(include_str!("../src/runtime.rs"), &["RawTransactionIngestHandle", "RawTransactionIngestWorker"]),
|
||||||
(include_str!("../src/snapshot.rs"), &["RawTransactionIngestSnapshot", "RawTransactionIngestSnapshotSource"]),
|
(include_str!("../src/snapshot.rs"), &["RawTransactionIngestSnapshot", "RawTransactionIngestSnapshotSource"]),
|
||||||
(include_str!("../src/persistence.rs"), &["RawTransactionIngestPersistenceOutcome", "RawTransactionIngestPersistencePort"]),
|
(include_str!("../src/persistence.rs"), &["RawTransactionIngestPersistenceOutcome", "RawTransactionIngestPersistencePort"]),
|
||||||
(include_str!("../src/runtime_resources.rs"), &["RawTransactionIngestYellowstoneSource", "RawTransactionIngestRuntimeResources"]),
|
(
|
||||||
|
include_str!("../src/runtime_resources.rs"),
|
||||||
|
&[
|
||||||
|
"RawTransactionIngestStandardBlockSource",
|
||||||
|
"RawTransactionIngestStandardLogsSource",
|
||||||
|
"RawTransactionIngestYellowstoneSource",
|
||||||
|
"RawTransactionIngestRuntimeResources",
|
||||||
|
],
|
||||||
|
),
|
||||||
];
|
];
|
||||||
for (source, symbols) in source_contracts {
|
for (source, symbols) in source_contracts {
|
||||||
for symbol in symbols {
|
for symbol in symbols {
|
||||||
@@ -207,8 +215,6 @@ fn pre_010_production_surface_has_no_historical_backfill_or_retriever_contract()
|
|||||||
"backfill",
|
"backfill",
|
||||||
"Checkpoint",
|
"Checkpoint",
|
||||||
"checkpoint",
|
"checkpoint",
|
||||||
"Discovery",
|
|
||||||
"discovery",
|
|
||||||
"historical",
|
"historical",
|
||||||
] {
|
] {
|
||||||
assert!(!source.contains(forbidden), "historical/retriever surface leaked into Worker production source: {forbidden}");
|
assert!(!source.contains(forbidden), "historical/retriever surface leaked into Worker production source: {forbidden}");
|
||||||
@@ -317,7 +323,8 @@ fn v0_3_12_pre_003_private_signal_debug_and_shape_do_not_expose_signature_filter
|
|||||||
fn v0_3_12_pre_004_hydration_provenance_and_remote_material_are_bounded_and_redacted() {
|
fn v0_3_12_pre_004_hydration_provenance_and_remote_material_are_bounded_and_redacted() {
|
||||||
let resources = include_str!("../src/runtime_resources.rs");
|
let resources = include_str!("../src/runtime_resources.rs");
|
||||||
for required in [
|
for required in [
|
||||||
"ys.{}:http.{}",
|
"route_prefix",
|
||||||
|
"{}.{}:http.{}",
|
||||||
"composite_provider_unrepresentable",
|
"composite_provider_unrepresentable",
|
||||||
"composite_endpoint_unrepresentable",
|
"composite_endpoint_unrepresentable",
|
||||||
"RawAcquisitionOrigin::Live",
|
"RawAcquisitionOrigin::Live",
|
||||||
@@ -390,8 +397,8 @@ fn v0_3_12_pre_006_runtime_resource_contract_opens_one_supervised_transport_sour
|
|||||||
assert!(resources.contains(required), "productive runtime-resource source behavior missing: {required}");
|
assert!(resources.contains(required), "productive runtime-resource source behavior missing: {required}");
|
||||||
}
|
}
|
||||||
assert!(runtime.contains("start_with_runtime_resources"));
|
assert!(runtime.contains("start_with_runtime_resources"));
|
||||||
assert!(runtime.contains("source.run(source_settings, stop_receiver, admission_sender, processing_frontier_sender)"));
|
assert!(runtime.contains("run_live_sources(source_settings, stop_receiver, admission_sender, processing_frontier_sender)"));
|
||||||
for forbidden in ["get_block_observed", "ksp_config_lib::", "ksp_store_postgres_lib::", "reqwest::", "tonic::", "yellowstone_grpc_proto::"] {
|
for forbidden in ["ksp_config_lib::", "ksp_store_postgres_lib::", "reqwest::", "tonic::", "yellowstone_grpc_proto::"] {
|
||||||
assert!(!resources.contains(forbidden) && !runtime.contains(forbidden), "pre.006 runtime source crossed a forbidden boundary: {forbidden}");
|
assert!(!resources.contains(forbidden) && !runtime.contains(forbidden), "pre.006 runtime source crossed a forbidden boundary: {forbidden}");
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -401,8 +408,11 @@ fn v0_3_12_pre_006_runtime_resource_contract_opens_one_supervised_transport_sour
|
|||||||
fn v0_3_12_pre_006_source_coalescence_is_bounded_stop_preemptible_and_redacted() {
|
fn v0_3_12_pre_006_source_coalescence_is_bounded_stop_preemptible_and_redacted() {
|
||||||
let resources = include_str!("../src/runtime_resources.rs");
|
let resources = include_str!("../src/runtime_resources.rs");
|
||||||
for required in [
|
for required in [
|
||||||
"max_in_flight: settings.persistence_concurrency()",
|
"max_in_flight: usize",
|
||||||
"max_pending_signals: crate::MAX_RAW_TRANSACTION_INGEST_ADMISSION_QUEUE_CAPACITY",
|
"max_pending_signals: usize",
|
||||||
|
"RawTransactionIngestHydrationCoordinator::with_global_registry(",
|
||||||
|
"hydration_pending_limit,",
|
||||||
|
"hydration_in_flight_limit,",
|
||||||
"pending_signal_count",
|
"pending_signal_count",
|
||||||
"stop_receiver.changed()",
|
"stop_receiver.changed()",
|
||||||
"tasks.abort_all()",
|
"tasks.abort_all()",
|
||||||
@@ -523,6 +533,168 @@ fn v0_3_12_pre_008_reconnect_projection_is_source_neutral_bounded_and_contains_n
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_003_standard_logs_redaction_and_reference_only_contract_are_explicit() {
|
||||||
|
let resources = include_str!("../src/runtime_resources.rs");
|
||||||
|
for required in [
|
||||||
|
"RawTransactionIngestStandardLogsSource",
|
||||||
|
".field(\"filter_kind\"",
|
||||||
|
".field(\"filter_fingerprint_bytes\"",
|
||||||
|
".field(\"source_key_bytes\"",
|
||||||
|
"project_standard_logs_signal",
|
||||||
|
"matched_filter_count: 1",
|
||||||
|
"transaction_index: std::option::Option::None",
|
||||||
|
"created_at: std::option::Option::None",
|
||||||
|
] {
|
||||||
|
assert!(resources.contains(required), "required pre.003 redaction/reference guard missing: {required}");
|
||||||
|
}
|
||||||
|
let source_struct = match resources.split_once("pub struct RawTransactionIngestStandardLogsSource {") {
|
||||||
|
std::option::Option::Some((_, tail)) => match tail.split_once("impl crate::RawTransactionIngestStandardLogsSource") {
|
||||||
|
std::option::Option::Some((value, _)) => value,
|
||||||
|
std::option::Option::None => "",
|
||||||
|
},
|
||||||
|
std::option::Option::None => "",
|
||||||
|
};
|
||||||
|
assert!(source_struct.contains("filter: ksp_onchain_transport_lib::SolanaLogsSubscribeFilter"));
|
||||||
|
for forbidden in ["logs:", "err:", "payload:", "body:", "url:"] {
|
||||||
|
assert!(!source_struct.contains(forbidden), "remote/sensitive material stored in standard logs source: {forbidden}");
|
||||||
|
}
|
||||||
|
let projection = match resources.split_once("fn project_standard_logs_signal") {
|
||||||
|
std::option::Option::Some((_, tail)) => match tail.split_once("fn route_yellowstone_update") {
|
||||||
|
std::option::Option::Some((value, _)) => value,
|
||||||
|
std::option::Option::None => "",
|
||||||
|
},
|
||||||
|
std::option::Option::None => "",
|
||||||
|
};
|
||||||
|
for forbidden in [".logs()", ".err()"] {
|
||||||
|
assert!(!projection.contains(forbidden), "logs/error material copied into standard logs projection: {forbidden}");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_004_standard_block_redaction_version_and_null_guards_are_explicit() {
|
||||||
|
let resources = include_str!("../src/runtime_resources.rs");
|
||||||
|
for required in [
|
||||||
|
"RawTransactionIngestStandardBlockSource",
|
||||||
|
".field(\"filter_kind\"",
|
||||||
|
".field(\"filter_fingerprint_bytes\"",
|
||||||
|
".field(\"source_key_bytes\"",
|
||||||
|
"source.standard_block_context_slot_mismatch",
|
||||||
|
"source.standard_block_remote_error",
|
||||||
|
"source.standard_block_missing",
|
||||||
|
"source.standard_block_transactions_missing",
|
||||||
|
"source.standard_block_transaction_version_unsupported",
|
||||||
|
"source.standard_block_transaction_version_unqualified",
|
||||||
|
"SolanaTransactionVersion::Number(0)",
|
||||||
|
"SolanaTransactionVersion::Number(1)",
|
||||||
|
] {
|
||||||
|
assert!(resources.contains(required), "required pre.004 standard block hardening guard missing: {required}");
|
||||||
|
}
|
||||||
|
let source_struct = match resources.split_once("pub struct RawTransactionIngestStandardBlockSource {") {
|
||||||
|
std::option::Option::Some((_, tail)) => match tail.split_once("impl crate::RawTransactionIngestStandardBlockSource") {
|
||||||
|
std::option::Option::Some((value, _)) => value,
|
||||||
|
std::option::Option::None => "",
|
||||||
|
},
|
||||||
|
std::option::Option::None => "",
|
||||||
|
};
|
||||||
|
for forbidden in ["err:", "block:", "transaction_data:", "url:"] {
|
||||||
|
assert!(!source_struct.contains(forbidden), "remote/raw material stored in standard block source: {forbidden}");
|
||||||
|
}
|
||||||
|
let source_impl = match resources.split_once("impl crate::RawTransactionIngestStandardBlockSource {") {
|
||||||
|
std::option::Option::Some((_, tail)) => match tail.split_once("impl std::fmt::Debug for crate::RawTransactionIngestStandardBlockSource") {
|
||||||
|
std::option::Option::Some((value, _)) => value,
|
||||||
|
std::option::Option::None => "",
|
||||||
|
},
|
||||||
|
std::option::Option::None => "",
|
||||||
|
};
|
||||||
|
assert!(!source_impl.contains("observe_pending("), "direct Standard Block source must not inflate hydration_pending");
|
||||||
|
assert!(source_impl.contains("processing_frontier.observe_settled(slot)"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_005_helius_transaction_redaction_full_reference_and_tier_neutrality_are_explicit() {
|
||||||
|
let resources = include_str!("../src/runtime_resources.rs");
|
||||||
|
for required in [
|
||||||
|
"RawTransactionIngestHeliusTransactionSource",
|
||||||
|
".field(\"filter\"",
|
||||||
|
".field(\"filter_fingerprint_bytes\"",
|
||||||
|
".field(\"source_key_bytes\"",
|
||||||
|
"HeliusTransactionNotification::Full(value)",
|
||||||
|
"source.helius_transaction_notification_unqualified",
|
||||||
|
"project_helius_transaction_signal",
|
||||||
|
"transaction_index: std::option::Option::Some(response.transaction_index())",
|
||||||
|
"RAW_TRANSACTION_INGEST_HELIUS_TRANSACTION_HTTP_PROTOCOL",
|
||||||
|
] {
|
||||||
|
assert!(resources.contains(required), "required pre.005 Helius hardening guard missing: {required}");
|
||||||
|
}
|
||||||
|
let source_struct = match resources.split_once("pub struct RawTransactionIngestHeliusTransactionSource {") {
|
||||||
|
std::option::Option::Some((_, tail)) => match tail.split_once("impl crate::RawTransactionIngestHeliusTransactionSource") {
|
||||||
|
std::option::Option::Some((value, _)) => value,
|
||||||
|
std::option::Option::None => "",
|
||||||
|
},
|
||||||
|
std::option::Option::None => "",
|
||||||
|
};
|
||||||
|
for forbidden in ["url:", "api_key", "credential", "transaction:", "payload:", "tier:"] {
|
||||||
|
assert!(!source_struct.contains(forbidden), "sensitive/provider material stored in Helius transaction source: {forbidden}");
|
||||||
|
}
|
||||||
|
let projection = match resources.split_once("fn project_helius_transaction_signal") {
|
||||||
|
std::option::Option::Some((_, tail)) => match tail.split_once("trait RawTransactionIngestStandardLogsView") {
|
||||||
|
std::option::Option::Some((value, _)) => value,
|
||||||
|
std::option::Option::None => "",
|
||||||
|
},
|
||||||
|
std::option::Option::None => "",
|
||||||
|
};
|
||||||
|
assert!(!projection.contains(".transaction()"), "Helius nested full payload must not enter Worker projection");
|
||||||
|
for forbidden in ["Developer", "Business", "Professional", "paid_tier", "provider_tier"] {
|
||||||
|
assert!(!resources.contains(forbidden), "provider tier must not be coded in Worker: {forbidden}");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_006_http_block_polling_is_bounded_run_local_stop_preemptible_and_redacted() {
|
||||||
|
let resources = include_str!("../src/runtime_resources.rs");
|
||||||
|
for required in [
|
||||||
|
"DEFAULT_RAW_TRANSACTION_INGEST_HTTP_POLL_INTERVAL",
|
||||||
|
"DEFAULT_RAW_TRANSACTION_INGEST_HTTP_POLL_MAX_BLOCKS_PER_CYCLE",
|
||||||
|
"MIN_RAW_TRANSACTION_INGEST_HTTP_POLL_INTERVAL",
|
||||||
|
"MAX_RAW_TRANSACTION_INGEST_HTTP_POLL_INTERVAL",
|
||||||
|
"MIN_RAW_TRANSACTION_INGEST_HTTP_POLL_MAX_BLOCKS_PER_CYCLE",
|
||||||
|
"MAX_RAW_TRANSACTION_INGEST_HTTP_POLL_MAX_BLOCKS_PER_CYCLE",
|
||||||
|
"let mut next_scan_slot = start_slot",
|
||||||
|
"next_scan_slot = slot",
|
||||||
|
"tokio::time::sleep(self.poll_interval)",
|
||||||
|
"processing_frontier.observe_settled(slot)",
|
||||||
|
"source.http_block_polling_transaction_version_unqualified",
|
||||||
|
"source.http_block_polling_transaction_version_unsupported",
|
||||||
|
] {
|
||||||
|
assert!(resources.contains(required), "required pre.006 HTTP polling hardening guard missing: {required}");
|
||||||
|
}
|
||||||
|
let source_impl = match resources.split_once("impl crate::RawTransactionIngestHttpBlockPollingSource {") {
|
||||||
|
std::option::Option::Some((_, tail)) => match tail.split_once("impl std::fmt::Debug for crate::RawTransactionIngestHttpBlockPollingSource") {
|
||||||
|
std::option::Option::Some((value, _)) => value,
|
||||||
|
std::option::Option::None => "",
|
||||||
|
},
|
||||||
|
std::option::Option::None => "",
|
||||||
|
};
|
||||||
|
assert_eq!(source_impl.matches("get_block_observed(").count(), 1);
|
||||||
|
assert!(!source_impl.contains("tokio::spawn"), "HTTP polling must not spawn one task per tick");
|
||||||
|
assert!(!source_impl.contains("observe_pending("), "HTTP block polling is direct RAW and must not inflate hydration_pending");
|
||||||
|
let source_struct = match resources.split_once("pub struct RawTransactionIngestHttpBlockPollingSource {") {
|
||||||
|
std::option::Option::Some((_, tail)) => match tail.split_once("impl crate::RawTransactionIngestHttpBlockPollingSource") {
|
||||||
|
std::option::Option::Some((value, _)) => value,
|
||||||
|
std::option::Option::None => "",
|
||||||
|
},
|
||||||
|
std::option::Option::None => "",
|
||||||
|
};
|
||||||
|
for forbidden in ["url:", "api_key", "credential", "secret", "transaction:", "payload:", "tier:"] {
|
||||||
|
assert!(!source_struct.contains(forbidden), "sensitive/raw material stored in HTTP polling source: {forbidden}");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn v0_3_12_pre_009_hydration_retry_ownership_and_no_orphan_cleanup_are_explicit() {
|
fn v0_3_12_pre_009_hydration_retry_ownership_and_no_orphan_cleanup_are_explicit() {
|
||||||
let resources = include_str!("../src/runtime_resources.rs");
|
let resources = include_str!("../src/runtime_resources.rs");
|
||||||
@@ -536,8 +708,669 @@ fn v0_3_12_pre_009_hydration_retry_ownership_and_no_orphan_cleanup_are_explicit(
|
|||||||
] {
|
] {
|
||||||
assert!(resources.contains(required), "required pre.009 no-orphan/backpressure guard missing: {required}");
|
assert!(resources.contains(required), "required pre.009 no-orphan/backpressure guard missing: {required}");
|
||||||
}
|
}
|
||||||
for forbidden in ["tokio::time::sleep", "tokio::time::interval", "get_block_observed", "unbounded_channel"] {
|
let hydration = match resources.split_once("struct RawTransactionIngestHydrationCoordinator {") {
|
||||||
assert!(!resources.contains(forbidden), "Worker introduced forbidden retry/unbounded behavior: {forbidden}");
|
std::option::Option::Some((_, tail)) => match tail.split_once("fn hydration_method_code(") {
|
||||||
|
std::option::Option::Some((value, _)) => value,
|
||||||
|
std::option::Option::None => "",
|
||||||
|
},
|
||||||
|
std::option::Option::None => "",
|
||||||
|
};
|
||||||
|
for forbidden in ["tokio::time::sleep", "tokio::time::interval", "get_block_observed"] {
|
||||||
|
assert!(!hydration.contains(forbidden), "hydration coordinator introduced forbidden retry behavior: {forbidden}");
|
||||||
|
}
|
||||||
|
assert!(!resources.contains("unbounded_channel"), "Worker introduced an unbounded channel");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_007_multi_source_supervisor_is_fail_closed_joined_and_does_not_publish_source_identity() {
|
||||||
|
let root = include_str!("../src/lib.rs");
|
||||||
|
let resources = include_str!("../src/runtime_resources.rs");
|
||||||
|
for required in [
|
||||||
|
"MAX_RAW_TRANSACTION_INGEST_LIVE_SOURCES",
|
||||||
|
"RawTransactionIngestSourceInventory",
|
||||||
|
"std::sync::Mutex",
|
||||||
|
"tokio::task::JoinSet",
|
||||||
|
"source.configured_source_closed",
|
||||||
|
"source.task_join_failed",
|
||||||
|
"source_stop_sender.send_replace(true)",
|
||||||
|
"drain_live_source_tasks",
|
||||||
|
"processing_frontier_slot = std::option::Option::None",
|
||||||
|
"saturating_add",
|
||||||
|
] {
|
||||||
|
assert!(resources.contains(required), "required pre.007 supervisor hardening guard missing: {required}");
|
||||||
|
}
|
||||||
|
for forbidden in [
|
||||||
|
"pub struct RawTransactionIngestSourceInventory",
|
||||||
|
"pub struct RawTransactionIngestSourceInventoryPublisher",
|
||||||
|
"pub fn source_key(",
|
||||||
|
"source_keys:",
|
||||||
|
"unbounded_channel",
|
||||||
|
"primary_source",
|
||||||
|
"standby_source",
|
||||||
|
"first_provider_wins",
|
||||||
|
] {
|
||||||
|
assert!(!root.contains(forbidden), "private source identity/scheduling policy leaked into public root: {forbidden}");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_008_cross_source_convergence_is_bounded_conflict_checked_and_private() {
|
||||||
|
let root = include_str!("../src/lib.rs");
|
||||||
|
let persistence = include_str!("../src/persistence.rs");
|
||||||
|
let resources = include_str!("../src/runtime_resources.rs");
|
||||||
|
for required in [
|
||||||
|
"max_entries: usize",
|
||||||
|
"entries.len() >= self.max_entries",
|
||||||
|
"std::sync::Arc::strong_count(entry) == 1",
|
||||||
|
"known_state_value != &canonical_state",
|
||||||
|
"record_observation(observation)",
|
||||||
|
"persistence.additional_observation_not_recorded",
|
||||||
|
] {
|
||||||
|
assert!(persistence.contains(required), "required pre.008 persistence hardening guard missing: {required}");
|
||||||
|
}
|
||||||
|
for required in [
|
||||||
|
"max_pending: usize",
|
||||||
|
"pending.len() >= self.max_pending",
|
||||||
|
"source.global_hydration_pending_saturated",
|
||||||
|
"tokio::sync::watch::channel",
|
||||||
|
"publish_and_remove",
|
||||||
|
] {
|
||||||
|
assert!(resources.contains(required), "required pre.008 hydration hardening guard missing: {required}");
|
||||||
|
}
|
||||||
|
for forbidden in [
|
||||||
|
"pub struct RawTransactionIngestPersistenceConvergence",
|
||||||
|
"pub struct RawTransactionIngestGlobalHydrationRegistry",
|
||||||
|
"pub fn persist_raw_transaction_ingest_converged_acquisition",
|
||||||
|
] {
|
||||||
|
assert!(!root.contains(forbidden), "pre.008 private convergence implementation leaked publicly: {forbidden}");
|
||||||
|
}
|
||||||
|
assert!(!resources.contains("unbounded_channel"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_009_duplicate_storm_disagreement_and_starvation_guards_are_explicit() {
|
||||||
|
let admission = include_str!("../src/admission.rs");
|
||||||
|
let persistence = include_str!("../src/persistence.rs");
|
||||||
|
let resources = include_str!("../src/runtime_resources.rs");
|
||||||
|
for required in [
|
||||||
|
"runtime_resources.hydration_pending_capacity_insufficient",
|
||||||
|
"runtime_resources.hydration_concurrency_insufficient",
|
||||||
|
"base + usize::from(hydration_entry_index < remainder)",
|
||||||
|
"hydration_in_flight_limit",
|
||||||
|
"tokio::sync::Semaphore::new(max_in_flight)",
|
||||||
|
"RawTransactionIngestHydrationLeaderGuard",
|
||||||
|
"source.global_hydration_pending_saturated",
|
||||||
|
"block_time: transaction.block_time()",
|
||||||
|
"slot: transaction.slot()",
|
||||||
|
"content_hash: transaction.payload().content_hash()",
|
||||||
|
] {
|
||||||
|
assert!(
|
||||||
|
admission.contains(required) || persistence.contains(required) || resources.contains(required),
|
||||||
|
"required pre.009 adversarial guard missing: {required}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
for forbidden in [
|
||||||
|
"VecDeque::new()",
|
||||||
|
"unbounded_channel",
|
||||||
|
"first_provider_wins",
|
||||||
|
"provider_priority",
|
||||||
|
"majority_vote",
|
||||||
|
"preferred_provider",
|
||||||
|
"overwrite_conflict",
|
||||||
|
] {
|
||||||
|
assert!(
|
||||||
|
!admission.contains(forbidden) && !persistence.contains(forbidden) && !resources.contains(forbidden),
|
||||||
|
"pre.009 introduced forbidden unbounded/provider-preference behavior: {forbidden}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_010_multi_source_health_is_conservative_counted_and_redacted() {
|
||||||
|
let snapshot = include_str!("../src/snapshot.rs");
|
||||||
|
let resources = include_str!("../src/runtime_resources.rs");
|
||||||
|
for required in [
|
||||||
|
"source_total: usize",
|
||||||
|
"source_active: usize",
|
||||||
|
"source_reconnecting: usize",
|
||||||
|
"source_failed: usize",
|
||||||
|
"source_reconnecting > 0",
|
||||||
|
"source_failed > 0",
|
||||||
|
"source_active < source_total",
|
||||||
|
"WorkerHealth::Degraded",
|
||||||
|
"WorkerHealth::Unhealthy",
|
||||||
|
] {
|
||||||
|
assert!(snapshot.contains(required), "required pre.010 health guard missing: {required}");
|
||||||
|
}
|
||||||
|
for required in ["source_total = self.source_projections.len()", "with_source_counts(source_total, source_active, source_reconnecting, source_failed)"] {
|
||||||
|
assert!(resources.contains(required), "required pre.010 inventory count guard missing: {required}");
|
||||||
|
}
|
||||||
|
for forbidden in ["source_key: [u8; 32]", "provider_url", "endpoint_url", "filter_fingerprint", "credential", "api_key", "token_header"] {
|
||||||
|
assert!(!snapshot.contains(forbidden), "pre.010 snapshot leaks source/provider material: {forbidden}");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_011_shutdown_races_are_bounded_joined_atomic_and_counter_safe() {
|
||||||
|
let runtime = include_str!("../src/runtime.rs");
|
||||||
|
let resources = include_str!("../src/runtime_resources.rs");
|
||||||
|
let supervisor = match runtime.split_once("async fn run_supervisor<Spawner>(") {
|
||||||
|
std::option::Option::Some((_, tail)) => match tail.split_once("fn spawn_persistence(") {
|
||||||
|
std::option::Option::Some((value, _)) => value,
|
||||||
|
std::option::Option::None => "",
|
||||||
|
},
|
||||||
|
std::option::Option::None => "",
|
||||||
|
};
|
||||||
|
let active_shutdown = match supervisor.split_once("let mut fault = supervise_until_stop(") {
|
||||||
|
std::option::Option::Some((_, value)) => value,
|
||||||
|
std::option::Option::None => "",
|
||||||
|
};
|
||||||
|
let stop_position = active_shutdown.find("source_stop_sender.send_replace(true);");
|
||||||
|
let stopping_position = active_shutdown.find("begin_stopping(");
|
||||||
|
let drain_position = active_shutdown.find("drain_owned_work(");
|
||||||
|
let terminal_position = active_shutdown.find("match fault {");
|
||||||
|
assert!(matches!((stop_position, stopping_position), (std::option::Option::Some(stop), std::option::Option::Some(stopping)) if stop < stopping));
|
||||||
|
assert!(matches!((stopping_position, drain_position), (std::option::Option::Some(stopping), std::option::Option::Some(drain)) if stopping < drain));
|
||||||
|
assert!(matches!((drain_position, terminal_position), (std::option::Option::Some(drain), std::option::Option::Some(terminal)) if drain < terminal));
|
||||||
|
for required in [
|
||||||
|
"tokio::time::timeout(settings.shutdown_drain_timeout(), drain).await",
|
||||||
|
"persistence.abort_all()",
|
||||||
|
"children.abort_all()",
|
||||||
|
"while persistence.join_next().await.is_some() {}",
|
||||||
|
"while children.join_next().await.is_some() {}",
|
||||||
|
] {
|
||||||
|
assert!(runtime.contains(required), "required pre.011 bounded-drain guard missing: {required}");
|
||||||
|
}
|
||||||
|
let registry_publish = match resources.split_once("fn publish_and_remove(&self, key: &RawTransactionIngestHydrationKey") {
|
||||||
|
std::option::Option::Some((_, tail)) => match tail.split_once("type RawTransactionIngestHydrationTasks") {
|
||||||
|
std::option::Option::Some((value, _)) => value,
|
||||||
|
std::option::Option::None => "",
|
||||||
|
},
|
||||||
|
std::option::Option::None => "",
|
||||||
|
};
|
||||||
|
let notify_position = registry_publish.find("sender.send_replace(std::option::Option::Some(result));");
|
||||||
|
let remove_position = registry_publish.find("pending.remove(key);");
|
||||||
|
assert!(matches!((notify_position, remove_position), (std::option::Option::Some(notify), std::option::Option::Some(remove)) if notify < remove));
|
||||||
|
let inventory = match resources.split_once("impl RawTransactionIngestSourceInventory {") {
|
||||||
|
std::option::Option::Some((_, tail)) => match tail.split_once("impl RawTransactionIngestSourceInventoryPublisher") {
|
||||||
|
std::option::Option::Some((value, _)) => value,
|
||||||
|
std::option::Option::None => "",
|
||||||
|
},
|
||||||
|
std::option::Option::None => "",
|
||||||
|
};
|
||||||
|
for required in [
|
||||||
|
"checked_add(projection.hydration_pending())",
|
||||||
|
"checked_add(projection.source_continuity_gap_total())",
|
||||||
|
"checked_add(projection.source_reconnect_total())",
|
||||||
|
"checked_add(projection.source_replay_attempt_total())",
|
||||||
|
"source.inventory_entry_missing",
|
||||||
|
"source.inventory_key_mismatch",
|
||||||
|
"source.inventory_projection_missing",
|
||||||
|
] {
|
||||||
|
assert!(inventory.contains(required), "required pre.011 inventory guard missing: {required}");
|
||||||
|
}
|
||||||
|
assert!(!inventory.contains("saturating_add"), "pre.011 source inventory must not silently saturate aggregate counters");
|
||||||
|
for source_marker in [
|
||||||
|
"impl crate::RawTransactionIngestYellowstoneSource",
|
||||||
|
"impl crate::RawTransactionIngestHeliusTransactionSource",
|
||||||
|
"impl crate::RawTransactionIngestHttpBlockPollingSource",
|
||||||
|
"impl crate::RawTransactionIngestStandardBlockSource",
|
||||||
|
"impl crate::RawTransactionIngestStandardLogsSource",
|
||||||
|
] {
|
||||||
|
let tail = match resources.split_once(source_marker) {
|
||||||
|
std::option::Option::Some((_, value)) => value,
|
||||||
|
std::option::Option::None => "",
|
||||||
|
};
|
||||||
|
assert!(tail.contains("stop_receiver.changed()"), "pre.011 source lacks stop-preemptible wait: {source_marker}");
|
||||||
|
}
|
||||||
|
assert!(!runtime.contains("unbounded_channel"));
|
||||||
|
assert!(!resources.contains("unbounded_channel"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_012_cross_layer_security_closure_is_complete_and_source_neutral() {
|
||||||
|
let cross_layer = include_str!("cross_layer_completeness.rs");
|
||||||
|
for required in [
|
||||||
|
"v0_3_13_pre_012_legacy_v0_v1_is_proven_from_transport_through_worker_common_raw_to_store",
|
||||||
|
"v0_3_13_pre_012_yellowstone_non_regression_covers_identity_v1_reconnect_gap_and_worker_hydration",
|
||||||
|
"v0_3_13_pre_012_security_redaction_matrix_covers_all_live_sources_and_lower_layers",
|
||||||
|
] {
|
||||||
|
assert!(cross_layer.contains(required), "pre.012 security closure proof missing: {required}");
|
||||||
|
}
|
||||||
|
let root = include_str!("../src/lib.rs");
|
||||||
|
for forbidden in [
|
||||||
|
"SourceSnapshotByKey",
|
||||||
|
"SourceHealthByKey",
|
||||||
|
"provider_health",
|
||||||
|
"endpoint_health",
|
||||||
|
"RawTransactionIngestGlobalHydrationRegistry",
|
||||||
|
"RawTransactionIngestCanonicalState",
|
||||||
|
"source_key",
|
||||||
|
] {
|
||||||
|
assert!(!root.contains(forbidden), "pre.012 public root leaked private/source-specific material: {forbidden}");
|
||||||
|
}
|
||||||
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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: 11
|
// version: 25
|
||||||
|
|
||||||
//! External public-surface proofs for the RAW transaction ingest Worker foundation.
|
//! External public-surface proofs for the RAW transaction ingest Worker foundation.
|
||||||
|
|
||||||
@@ -182,6 +182,137 @@ fn v0_3_12_pre_002_runtime_resource_types_are_consumable_without_client_escape_h
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_002_multi_source_runtime_resource_surface_is_bounded_and_source_neutral() {
|
||||||
|
let _source_count: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources) -> usize =
|
||||||
|
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::source_count;
|
||||||
|
let _push_yellowstone: fn(
|
||||||
|
&mut ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources,
|
||||||
|
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestYellowstoneSource,
|
||||||
|
) -> ksp_core_lib::Result<()> = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::try_push_yellowstone_source;
|
||||||
|
assert_eq!(ksp_worker_raw_transaction_ingest_lib::MAX_RAW_TRANSACTION_INGEST_LIVE_SOURCES, 32);
|
||||||
|
let root = include_str!("../src/lib.rs");
|
||||||
|
assert!(!root.contains("RawTransactionIngestLiveSource"));
|
||||||
|
assert!(!root.contains("source_key"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_003_standard_logs_runtime_resource_surface_is_typed_and_transport_owned() {
|
||||||
|
let _source_new: fn(
|
||||||
|
ksp_onchain_transport_lib::WsEndpointSettings,
|
||||||
|
ksp_onchain_transport_lib::SolanaLogsSubscribeFilter,
|
||||||
|
ksp_onchain_transport_lib::SolanaCommitment,
|
||||||
|
ksp_onchain_transport_lib::HttpTransportPool,
|
||||||
|
ksp_onchain_transport_lib::HttpRoleName,
|
||||||
|
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestStandardLogsSource> =
|
||||||
|
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestStandardLogsSource::new;
|
||||||
|
let _resources_from_logs: fn(
|
||||||
|
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestStandardLogsSource,
|
||||||
|
) -> ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources =
|
||||||
|
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::from_standard_logs_source;
|
||||||
|
let _push_logs: fn(
|
||||||
|
&mut ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources,
|
||||||
|
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestStandardLogsSource,
|
||||||
|
) -> ksp_core_lib::Result<()> = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::try_push_standard_logs_source;
|
||||||
|
let root = include_str!("../src/lib.rs");
|
||||||
|
let resources = include_str!("../src/runtime_resources.rs");
|
||||||
|
assert!(root.contains("RawTransactionIngestStandardLogsSource"));
|
||||||
|
for forbidden in ["pub fn ws_endpoint(", "pub fn filter(", "pub fn http_pool(", "pub fn source_key("] {
|
||||||
|
assert!(!resources.contains(forbidden), "standard logs source implementation escape hatch present: {forbidden}");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_005_helius_transaction_runtime_resource_surface_is_typed_and_transport_owned() {
|
||||||
|
let _source_new: fn(
|
||||||
|
ksp_onchain_transport_lib::WsEndpointSettings,
|
||||||
|
ksp_onchain_transport_lib::HeliusTransactionSubscribeFilter,
|
||||||
|
ksp_onchain_transport_lib::SolanaCommitment,
|
||||||
|
ksp_onchain_transport_lib::HttpTransportPool,
|
||||||
|
ksp_onchain_transport_lib::HttpRoleName,
|
||||||
|
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHeliusTransactionSource> =
|
||||||
|
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHeliusTransactionSource::new;
|
||||||
|
let _resources_from_helius: fn(
|
||||||
|
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHeliusTransactionSource,
|
||||||
|
) -> ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources =
|
||||||
|
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::from_helius_transaction_source;
|
||||||
|
let _push_helius: fn(
|
||||||
|
&mut ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources,
|
||||||
|
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHeliusTransactionSource,
|
||||||
|
) -> ksp_core_lib::Result<()> = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::try_push_helius_transaction_source;
|
||||||
|
let root = include_str!("../src/lib.rs");
|
||||||
|
let resources = include_str!("../src/runtime_resources.rs");
|
||||||
|
assert!(root.contains("RawTransactionIngestHeliusTransactionSource"));
|
||||||
|
for forbidden in ["pub fn ws_endpoint(", "pub fn filter(", "pub fn http_pool(", "pub fn source_key("] {
|
||||||
|
assert!(!resources.contains(forbidden), "Helius transaction source implementation escape hatch present: {forbidden}");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_004_standard_block_runtime_resource_surface_is_typed_and_transport_owned() {
|
||||||
|
let _source_new: fn(
|
||||||
|
ksp_onchain_transport_lib::WsEndpointSettings,
|
||||||
|
ksp_onchain_transport_lib::SolanaBlockSubscribeFilter,
|
||||||
|
ksp_onchain_transport_lib::SolanaCommitment,
|
||||||
|
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestStandardBlockSource> =
|
||||||
|
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestStandardBlockSource::new;
|
||||||
|
let _resources_from_block: fn(
|
||||||
|
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestStandardBlockSource,
|
||||||
|
) -> ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources =
|
||||||
|
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::from_standard_block_source;
|
||||||
|
let _push_block: fn(
|
||||||
|
&mut ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources,
|
||||||
|
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestStandardBlockSource,
|
||||||
|
) -> ksp_core_lib::Result<()> = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::try_push_standard_block_source;
|
||||||
|
let root = include_str!("../src/lib.rs");
|
||||||
|
let resources = include_str!("../src/runtime_resources.rs");
|
||||||
|
assert!(root.contains("RawTransactionIngestStandardBlockSource"));
|
||||||
|
for forbidden in ["pub fn ws_endpoint(", "pub fn filter(", "pub fn source_key("] {
|
||||||
|
assert!(!resources.contains(forbidden), "standard block source implementation escape hatch present: {forbidden}");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_006_http_block_polling_runtime_resource_surface_is_bounded_and_transport_owned() {
|
||||||
|
let _source_new: fn(
|
||||||
|
ksp_onchain_transport_lib::HttpTransportPool,
|
||||||
|
ksp_onchain_transport_lib::HttpRoleName,
|
||||||
|
ksp_onchain_transport_lib::SolanaCommitment,
|
||||||
|
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHttpBlockPollingSource> =
|
||||||
|
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHttpBlockPollingSource::new;
|
||||||
|
let _source_new_with_limits: fn(
|
||||||
|
ksp_onchain_transport_lib::HttpTransportPool,
|
||||||
|
ksp_onchain_transport_lib::HttpRoleName,
|
||||||
|
ksp_onchain_transport_lib::SolanaCommitment,
|
||||||
|
std::time::Duration,
|
||||||
|
u16,
|
||||||
|
) -> ksp_core_lib::Result<ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHttpBlockPollingSource> =
|
||||||
|
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHttpBlockPollingSource::new_with_limits;
|
||||||
|
let _resources_from_polling: fn(
|
||||||
|
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHttpBlockPollingSource,
|
||||||
|
) -> ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources =
|
||||||
|
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::from_http_block_polling_source;
|
||||||
|
let _push_polling: fn(
|
||||||
|
&mut ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources,
|
||||||
|
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestHttpBlockPollingSource,
|
||||||
|
) -> ksp_core_lib::Result<()> = ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestRuntimeResources::try_push_http_block_polling_source;
|
||||||
|
assert_eq!(ksp_worker_raw_transaction_ingest_lib::DEFAULT_RAW_TRANSACTION_INGEST_HTTP_POLL_INTERVAL, std::time::Duration::from_secs(1));
|
||||||
|
assert_eq!(ksp_worker_raw_transaction_ingest_lib::DEFAULT_RAW_TRANSACTION_INGEST_HTTP_POLL_MAX_BLOCKS_PER_CYCLE, 128);
|
||||||
|
assert_eq!(ksp_worker_raw_transaction_ingest_lib::MIN_RAW_TRANSACTION_INGEST_HTTP_POLL_INTERVAL, std::time::Duration::from_millis(100));
|
||||||
|
assert_eq!(ksp_worker_raw_transaction_ingest_lib::MAX_RAW_TRANSACTION_INGEST_HTTP_POLL_INTERVAL, std::time::Duration::from_secs(30));
|
||||||
|
assert_eq!(ksp_worker_raw_transaction_ingest_lib::MIN_RAW_TRANSACTION_INGEST_HTTP_POLL_MAX_BLOCKS_PER_CYCLE, 1);
|
||||||
|
assert_eq!(ksp_worker_raw_transaction_ingest_lib::MAX_RAW_TRANSACTION_INGEST_HTTP_POLL_MAX_BLOCKS_PER_CYCLE, 1024);
|
||||||
|
let resources = include_str!("../src/runtime_resources.rs");
|
||||||
|
for forbidden in ["pub fn http_pool(", "pub fn polling_role(", "pub fn source_key(", "pub fn profile_fingerprint("] {
|
||||||
|
assert!(!resources.contains(forbidden), "HTTP polling source implementation escape hatch present: {forbidden}");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn v0_3_12_pre_007_processing_frontier_snapshot_getters_are_public_and_processing_only() {
|
fn v0_3_12_pre_007_processing_frontier_snapshot_getters_are_public_and_processing_only() {
|
||||||
let _hydration_pending: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> usize =
|
let _hydration_pending: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> usize =
|
||||||
@@ -196,3 +327,188 @@ fn v0_3_12_pre_007_processing_frontier_snapshot_getters_are_public_and_processin
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_007_source_inventory_and_logical_keys_remain_private() {
|
||||||
|
let root = include_str!("../src/lib.rs");
|
||||||
|
for forbidden in ["RawTransactionIngestSourceInventory", "RawTransactionIngestSourceInventoryPublisher", "RawTransactionIngestLiveSource", "source_key"] {
|
||||||
|
assert!(!root.contains(forbidden), "pre.007 private multi-source implementation leaked through crate root: {forbidden}");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_008_convergence_cache_registry_and_source_keys_remain_private() {
|
||||||
|
let root = include_str!("../src/lib.rs");
|
||||||
|
for forbidden in [
|
||||||
|
"pub use self::persistence::RawTransactionIngestPersistenceConvergence",
|
||||||
|
"pub use self::persistence::persist_raw_transaction_ingest_converged_acquisition",
|
||||||
|
"RawTransactionIngestGlobalHydrationRegistry",
|
||||||
|
"RawTransactionIngestHydrationKey",
|
||||||
|
"source_key",
|
||||||
|
] {
|
||||||
|
assert!(!root.contains(forbidden), "pre.008 convergence implementation leaked through public crate root: {forbidden}");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_009_fairness_budgets_semaphore_and_canonical_state_remain_private() {
|
||||||
|
let root = include_str!("../src/lib.rs");
|
||||||
|
for private_name in [
|
||||||
|
"RawTransactionIngestCanonicalState",
|
||||||
|
"RawTransactionIngestGlobalHydrationRegistry",
|
||||||
|
"RawTransactionIngestHydrationLeaderGuard",
|
||||||
|
"validate_hydration_fairness_capacity",
|
||||||
|
"hydration_pending_limit",
|
||||||
|
"hydration_in_flight_limit",
|
||||||
|
] {
|
||||||
|
assert!(!root.contains(&std::format!("pub use self::runtime_resources::{private_name}")));
|
||||||
|
assert!(!root.contains(&std::format!("pub use self::persistence::{private_name}")));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_010_multi_source_snapshot_counts_are_public_and_source_neutral() {
|
||||||
|
let _source_total: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> usize =
|
||||||
|
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::source_total;
|
||||||
|
let _source_active: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> usize =
|
||||||
|
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::source_active;
|
||||||
|
let _source_reconnecting: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> usize =
|
||||||
|
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::source_reconnecting;
|
||||||
|
let _source_failed: fn(&ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot) -> usize =
|
||||||
|
ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshot::source_failed;
|
||||||
|
let root = include_str!("../src/lib.rs");
|
||||||
|
for forbidden in ["SourceHealthByKey", "SourceSnapshotByKey", "provider_health", "endpoint_health"] {
|
||||||
|
assert!(!root.contains(forbidden), "pre.010 public root leaked source-specific health material: {forbidden}");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_011_race_hardening_adds_no_public_runtime_or_source_identity_surface() {
|
||||||
|
let root = include_str!("../src/lib.rs");
|
||||||
|
for forbidden in [
|
||||||
|
"pub use self::runtime_resources::RawTransactionIngestSourceInventory",
|
||||||
|
"pub use self::runtime_resources::RawTransactionIngestSourceInventoryPublisher",
|
||||||
|
"pub use self::runtime_resources::RawTransactionIngestGlobalHydrationRegistry",
|
||||||
|
"pub use self::runtime_resources::RawTransactionIngestHydrationLeaderGuard",
|
||||||
|
"publish_and_remove",
|
||||||
|
"source.inventory_key_mismatch",
|
||||||
|
] {
|
||||||
|
assert!(!root.contains(forbidden), "pre.011 private race-hardening implementation leaked publicly: {forbidden}");
|
||||||
|
}
|
||||||
|
assert_eq!(
|
||||||
|
ksp_worker_raw_transaction_ingest_lib::ERROR_CODE_RAW_TRANSACTION_INGEST_COUNTER_EXHAUSTED,
|
||||||
|
ksp_core_lib::ErrorCode::new("worker_raw_transaction_ingest", "counter_exhausted")
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
ksp_worker_raw_transaction_ingest_lib::ERROR_CODE_RAW_TRANSACTION_INGEST_DRAIN_TIMEOUT,
|
||||||
|
ksp_core_lib::ErrorCode::new("worker_raw_transaction_ingest", "drain_timeout")
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_012_completeness_closure_adds_no_public_implementation_surface() {
|
||||||
|
let root = include_str!("../src/lib.rs");
|
||||||
|
assert!(!root.contains("pub mod "));
|
||||||
|
for forbidden in [
|
||||||
|
"pub use self::runtime_resources::RawTransactionIngestLiveSource",
|
||||||
|
"pub use self::runtime_resources::RawTransactionIngestSourceInventory",
|
||||||
|
"pub use self::runtime_resources::RawTransactionIngestGlobalHydrationRegistry",
|
||||||
|
"pub use self::persistence::RawTransactionIngestCanonicalState",
|
||||||
|
"pub use self::persistence::RawTransactionIngestPersistenceConvergence",
|
||||||
|
] {
|
||||||
|
assert!(!root.contains(forbidden), "pre.012 private implementation leaked through public root: {forbidden}");
|
||||||
|
}
|
||||||
|
let cross_layer = include_str!("cross_layer_completeness.rs");
|
||||||
|
assert!(cross_layer.contains("v0_3_13_pre_012_all_five_live_source_families_share_one_worker_common_raw_store_pipeline"));
|
||||||
|
assert!(cross_layer.contains("v0_3_13_pre_012_legacy_v0_v1_is_proven_from_transport_through_worker_common_raw_to_store"));
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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: 9
|
// version: 32
|
||||||
|
|
||||||
//! Release-completeness canaries through the `pre.010` public/release/security hardening 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() {
|
||||||
@@ -60,6 +71,8 @@ fn pre_010_public_root_export_inventory_is_exact() {
|
|||||||
exports,
|
exports,
|
||||||
std::vec![
|
std::vec![
|
||||||
"DEFAULT_RAW_TRANSACTION_INGEST_ADMISSION_QUEUE_CAPACITY",
|
"DEFAULT_RAW_TRANSACTION_INGEST_ADMISSION_QUEUE_CAPACITY",
|
||||||
|
"DEFAULT_RAW_TRANSACTION_INGEST_HTTP_POLL_INTERVAL",
|
||||||
|
"DEFAULT_RAW_TRANSACTION_INGEST_HTTP_POLL_MAX_BLOCKS_PER_CYCLE",
|
||||||
"DEFAULT_RAW_TRANSACTION_INGEST_PERSISTENCE_CONCURRENCY",
|
"DEFAULT_RAW_TRANSACTION_INGEST_PERSISTENCE_CONCURRENCY",
|
||||||
"DEFAULT_RAW_TRANSACTION_INGEST_SHUTDOWN_DRAIN_TIMEOUT",
|
"DEFAULT_RAW_TRANSACTION_INGEST_SHUTDOWN_DRAIN_TIMEOUT",
|
||||||
"ERROR_CODE_RAW_TRANSACTION_INGEST_CONTENT_CONFLICT",
|
"ERROR_CODE_RAW_TRANSACTION_INGEST_CONTENT_CONFLICT",
|
||||||
@@ -70,19 +83,33 @@ fn pre_010_public_root_export_inventory_is_exact() {
|
|||||||
"ERROR_CODE_RAW_TRANSACTION_INGEST_SOURCE_FAILED",
|
"ERROR_CODE_RAW_TRANSACTION_INGEST_SOURCE_FAILED",
|
||||||
"ERROR_CODE_RAW_TRANSACTION_INGEST_STORE_FAILED",
|
"ERROR_CODE_RAW_TRANSACTION_INGEST_STORE_FAILED",
|
||||||
"MAX_RAW_TRANSACTION_INGEST_ADMISSION_QUEUE_CAPACITY",
|
"MAX_RAW_TRANSACTION_INGEST_ADMISSION_QUEUE_CAPACITY",
|
||||||
|
"MAX_RAW_TRANSACTION_INGEST_HTTP_POLL_INTERVAL",
|
||||||
|
"MAX_RAW_TRANSACTION_INGEST_HTTP_POLL_MAX_BLOCKS_PER_CYCLE",
|
||||||
|
"MAX_RAW_TRANSACTION_INGEST_LIVE_SOURCES",
|
||||||
"MAX_RAW_TRANSACTION_INGEST_PERSISTENCE_CONCURRENCY",
|
"MAX_RAW_TRANSACTION_INGEST_PERSISTENCE_CONCURRENCY",
|
||||||
"MAX_RAW_TRANSACTION_INGEST_SHUTDOWN_DRAIN_TIMEOUT",
|
"MAX_RAW_TRANSACTION_INGEST_SHUTDOWN_DRAIN_TIMEOUT",
|
||||||
"MIN_RAW_TRANSACTION_INGEST_ADMISSION_QUEUE_CAPACITY",
|
"MIN_RAW_TRANSACTION_INGEST_ADMISSION_QUEUE_CAPACITY",
|
||||||
|
"MIN_RAW_TRANSACTION_INGEST_HTTP_POLL_INTERVAL",
|
||||||
|
"MIN_RAW_TRANSACTION_INGEST_HTTP_POLL_MAX_BLOCKS_PER_CYCLE",
|
||||||
"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",
|
||||||
|
"RawTransactionIngestHttpBlockPollingSource",
|
||||||
|
"RawTransactionIngestRepairMethod",
|
||||||
"RawTransactionIngestRuntimeResources",
|
"RawTransactionIngestRuntimeResources",
|
||||||
"RawTransactionIngestSettings",
|
"RawTransactionIngestSettings",
|
||||||
"RawTransactionIngestSnapshot",
|
"RawTransactionIngestSnapshot",
|
||||||
"RawTransactionIngestSnapshotFuture",
|
"RawTransactionIngestSnapshotFuture",
|
||||||
"RawTransactionIngestSnapshotSource",
|
"RawTransactionIngestSnapshotSource",
|
||||||
"RawTransactionIngestSourceState",
|
"RawTransactionIngestSourceState",
|
||||||
|
"RawTransactionIngestStandardBlockSource",
|
||||||
|
"RawTransactionIngestStandardLogsSource",
|
||||||
"RawTransactionIngestTerminalFuture",
|
"RawTransactionIngestTerminalFuture",
|
||||||
"RawTransactionIngestWorker",
|
"RawTransactionIngestWorker",
|
||||||
"RawTransactionIngestYellowstoneSource",
|
"RawTransactionIngestYellowstoneSource",
|
||||||
@@ -112,6 +139,17 @@ fn pre_010_external_hardening_suite_is_present_and_scoped() {
|
|||||||
"v0_3_12_pre_009_hydration_retry_ownership_and_no_orphan_cleanup_are_explicit",
|
"v0_3_12_pre_009_hydration_retry_ownership_and_no_orphan_cleanup_are_explicit",
|
||||||
"pre_010_lower_layers_have_no_dependency_return_to_concrete_worker",
|
"pre_010_lower_layers_have_no_dependency_return_to_concrete_worker",
|
||||||
"v0_3_12_pre_002_public_root_exposes_contract_types_without_transport_implementation_paths",
|
"v0_3_12_pre_002_public_root_exposes_contract_types_without_transport_implementation_paths",
|
||||||
|
"v0_3_13_pre_003_standard_logs_redaction_and_reference_only_contract_are_explicit",
|
||||||
|
"v0_3_13_pre_004_standard_block_redaction_version_and_null_guards_are_explicit",
|
||||||
|
"v0_3_13_pre_005_helius_transaction_redaction_full_reference_and_tier_neutrality_are_explicit",
|
||||||
|
"v0_3_13_pre_006_http_block_polling_is_bounded_run_local_stop_preemptible_and_redacted",
|
||||||
|
"v0_3_13_pre_007_multi_source_supervisor_is_fail_closed_joined_and_does_not_publish_source_identity",
|
||||||
|
"v0_3_13_pre_008_cross_source_convergence_is_bounded_conflict_checked_and_private",
|
||||||
|
"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_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}");
|
||||||
}
|
}
|
||||||
@@ -125,12 +163,30 @@ fn pre_010_external_hardening_suite_is_present_and_scoped() {
|
|||||||
assert!(dependency_boundary.contains("v0_3_12_pre_005_block_and_continuity_adapters_remain_private_and_transport_facade_only"));
|
assert!(dependency_boundary.contains("v0_3_12_pre_005_block_and_continuity_adapters_remain_private_and_transport_facade_only"));
|
||||||
assert!(dependency_boundary.contains("v0_3_12_pre_007_processing_frontier_remains_run_local_and_backend_neutral_after_continuity_extension"));
|
assert!(dependency_boundary.contains("v0_3_12_pre_007_processing_frontier_remains_run_local_and_backend_neutral_after_continuity_extension"));
|
||||||
assert!(dependency_boundary.contains("v0_3_12_pre_008_worker_observes_transport_reconnect_replay_and_faults_only_on_proven_retention_gap"));
|
assert!(dependency_boundary.contains("v0_3_12_pre_008_worker_observes_transport_reconnect_replay_and_faults_only_on_proven_retention_gap"));
|
||||||
|
assert!(dependency_boundary.contains("v0_3_13_pre_003_standard_logs_source_reuses_transport_facades_and_common_hydration_only"));
|
||||||
|
assert!(dependency_boundary.contains("v0_3_13_pre_004_standard_block_source_is_direct_raw_and_transport_facade_only"));
|
||||||
|
assert!(dependency_boundary.contains("v0_3_13_pre_005_helius_transaction_source_reuses_transport_facade_and_common_hydration_only"));
|
||||||
|
assert!(dependency_boundary.contains("v0_3_13_pre_006_http_block_polling_reuses_transport_facades_without_becoming_backfill"));
|
||||||
|
assert!(dependency_boundary.contains("v0_3_13_pre_007_multi_source_supervisor_and_inventory_remain_private_bounded_and_source_neutral"));
|
||||||
|
assert!(dependency_boundary.contains("v0_3_13_pre_008_cross_source_convergence_reuses_store_and_transport_facades_only"));
|
||||||
|
assert!(dependency_boundary.contains("v0_3_13_pre_009_global_bounds_and_fairness_stay_inside_worker_facades"));
|
||||||
|
assert!(dependency_boundary.contains("v0_3_13_pre_010_multi_source_snapshot_health_remains_source_neutral_and_backend_free"));
|
||||||
|
assert!(dependency_boundary.contains("v0_3_13_pre_011_shutdown_race_hardening_stays_inside_worker_and_existing_facades"));
|
||||||
let public_api = include_str!("public_api.rs");
|
let public_api = include_str!("public_api.rs");
|
||||||
assert!(public_api.contains("pre_003_kind_code_and_settings_are_consumable_from_crate_root"));
|
assert!(public_api.contains("pre_003_kind_code_and_settings_are_consumable_from_crate_root"));
|
||||||
assert!(public_api.contains("pre_004_start_handle_and_terminal_future_are_consumable_without_public_join_handle"));
|
assert!(public_api.contains("pre_004_start_handle_and_terminal_future_are_consumable_without_public_join_handle"));
|
||||||
assert!(public_api.contains("pre_008_snapshot_surface_and_common_projection_are_public_and_stable"));
|
assert!(public_api.contains("pre_008_snapshot_surface_and_common_projection_are_public_and_stable"));
|
||||||
assert!(public_api.contains("pre_009_source_and_drain_timeout_error_codes_are_public_and_stable"));
|
assert!(public_api.contains("pre_009_source_and_drain_timeout_error_codes_are_public_and_stable"));
|
||||||
assert!(public_api.contains("v0_3_12_pre_002_runtime_resource_types_are_consumable_without_client_escape_hatch"));
|
assert!(public_api.contains("v0_3_12_pre_002_runtime_resource_types_are_consumable_without_client_escape_hatch"));
|
||||||
|
assert!(public_api.contains("v0_3_13_pre_002_multi_source_runtime_resource_surface_is_bounded_and_source_neutral"));
|
||||||
|
assert!(public_api.contains("v0_3_13_pre_003_standard_logs_runtime_resource_surface_is_typed_and_transport_owned"));
|
||||||
|
assert!(public_api.contains("v0_3_13_pre_004_standard_block_runtime_resource_surface_is_typed_and_transport_owned"));
|
||||||
|
assert!(public_api.contains("v0_3_13_pre_006_http_block_polling_runtime_resource_surface_is_bounded_and_transport_owned"));
|
||||||
|
assert!(public_api.contains("v0_3_13_pre_007_source_inventory_and_logical_keys_remain_private"));
|
||||||
|
assert!(public_api.contains("v0_3_13_pre_008_convergence_cache_registry_and_source_keys_remain_private"));
|
||||||
|
assert!(public_api.contains("v0_3_13_pre_009_fairness_budgets_semaphore_and_canonical_state_remain_private"));
|
||||||
|
assert!(public_api.contains("v0_3_13_pre_010_multi_source_snapshot_counts_are_public_and_source_neutral"));
|
||||||
|
assert!(public_api.contains("v0_3_13_pre_011_race_hardening_adds_no_public_runtime_or_source_identity_surface"));
|
||||||
assert!(public_api.contains("v0_3_12_pre_007_processing_frontier_snapshot_getters_are_public_and_processing_only"));
|
assert!(public_api.contains("v0_3_12_pre_007_processing_frontier_snapshot_getters_are_public_and_processing_only"));
|
||||||
assert!(public_api.contains("pre_008_snapshot_surface_and_common_projection_are_public_and_stable"));
|
assert!(public_api.contains("pre_008_snapshot_surface_and_common_projection_are_public_and_stable"));
|
||||||
return;
|
return;
|
||||||
@@ -177,3 +233,233 @@ fn v0_3_12_pre_010_cross_layer_completeness_suite_is_present_and_exact() -> std:
|
|||||||
}
|
}
|
||||||
return std::result::Result::Ok(());
|
return std::result::Result::Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_012_cross_layer_completeness_security_inventory_is_exact() {
|
||||||
|
let cross_layer = include_str!("cross_layer_completeness.rs");
|
||||||
|
for required in [
|
||||||
|
"v0_3_13_pre_012_all_five_live_source_families_share_one_worker_common_raw_store_pipeline",
|
||||||
|
"v0_3_13_pre_012_legacy_v0_v1_is_proven_from_transport_through_worker_common_raw_to_store",
|
||||||
|
"v0_3_13_pre_012_yellowstone_non_regression_covers_identity_v1_reconnect_gap_and_worker_hydration",
|
||||||
|
"v0_3_13_pre_012_security_redaction_matrix_covers_all_live_sources_and_lower_layers",
|
||||||
|
] {
|
||||||
|
assert!(cross_layer.contains(required), "required pre.012 cross-layer canary missing: {required}");
|
||||||
|
}
|
||||||
|
let dependency_boundary = include_str!("dependency_boundary.rs");
|
||||||
|
let hardening = include_str!("hardening.rs");
|
||||||
|
let public_api = include_str!("public_api.rs");
|
||||||
|
assert!(dependency_boundary.contains("v0_3_13_pre_012_cross_layer_closure_does_not_expand_worker_dependency_graph"));
|
||||||
|
assert!(hardening.contains("v0_3_13_pre_012_cross_layer_security_closure_is_complete_and_source_neutral"));
|
||||||
|
assert!(public_api.contains("v0_3_13_pre_012_completeness_closure_adds_no_public_implementation_surface"));
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/admission.rs
|
// file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/admission.rs
|
||||||
// version: 4
|
// version: 6
|
||||||
|
|
||||||
fn material(
|
fn material(
|
||||||
network: &str,
|
network: &str,
|
||||||
@@ -262,3 +262,153 @@ async fn pre_009_full_queue_dequeue_marks_source_neutral_backpressure_observatio
|
|||||||
assert!(!admission.take_backpressure_wait_observed());
|
assert!(!admission.take_backpressure_wait_observed());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn v0_3_13_pre_008_distinct_source_keys_produce_distinct_idempotent_observation_keys() {
|
||||||
|
let (network, first_material) = match material("mainnet", 51, "AQID") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let (_, second_material) = match material("mainnet", 51, "AQID") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let first_provenance = match provenance() {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let second_provenance = match provenance() {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let (mut admission, sender) = crate::RawTransactionAdmission::new(2);
|
||||||
|
let first_ingress = crate::RawTransactionIngress {
|
||||||
|
material: first_material,
|
||||||
|
network: network.clone(),
|
||||||
|
provenance: first_provenance,
|
||||||
|
source_key: [11_u8; 32],
|
||||||
|
};
|
||||||
|
let second_ingress = crate::RawTransactionIngress {
|
||||||
|
material: second_material,
|
||||||
|
network: network.clone(),
|
||||||
|
provenance: second_provenance,
|
||||||
|
source_key: [12_u8; 32],
|
||||||
|
};
|
||||||
|
if sender.send(first_ingress).await.is_err() || sender.send(second_ingress).await.is_err() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::mem::drop(sender);
|
||||||
|
let first = match admission.receive(&network).await {
|
||||||
|
std::result::Result::Ok(std::option::Option::Some(value)) => value,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
let second = match admission.receive(&network).await {
|
||||||
|
std::result::Result::Ok(std::option::Option::Some(value)) => value,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
assert_eq!(first.transaction().reference(), second.transaction().reference());
|
||||||
|
assert_eq!(first.transaction().payload().content_hash(), second.transaction().payload().content_hash());
|
||||||
|
assert_ne!(first.observation().observation_key(), second.observation().observation_key());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn v0_3_13_pre_009_duplicate_storm_cannot_starve_second_ready_source_on_bounded_admission() {
|
||||||
|
let (network, initial_material) = match material("mainnet", 60, "AQID") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let (_, peer_material) = match material("mainnet", 99, "BwgJ") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let initial_provenance = match provenance() {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let storm_provenance = match provenance() {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let peer_provenance = match provenance() {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let (mut admission, sender) = crate::RawTransactionAdmission::new(1);
|
||||||
|
assert!(
|
||||||
|
sender
|
||||||
|
.send(crate::RawTransactionIngress {
|
||||||
|
material: initial_material,
|
||||||
|
network: network.clone(),
|
||||||
|
provenance: initial_provenance,
|
||||||
|
source_key: [60_u8; 32],
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.is_ok()
|
||||||
|
);
|
||||||
|
let storm_sender = sender.clone();
|
||||||
|
let storm_network = network.clone();
|
||||||
|
let storm_task = tokio::spawn(async move {
|
||||||
|
for _ in 0..8 {
|
||||||
|
let material = ksp_raw_transaction_lib::RawTransactionMaterial::binary_base64(
|
||||||
|
storm_network.clone(),
|
||||||
|
ksp_store_lib::RawTransactionSignature::new([61_u8; 64]),
|
||||||
|
42,
|
||||||
|
std::option::Option::Some(1_700_000_000),
|
||||||
|
"BAUG",
|
||||||
|
ksp_raw_transaction_lib::RawTransactionWireField::Omitted,
|
||||||
|
ksp_raw_transaction_lib::RawTransactionWireField::Omitted,
|
||||||
|
ksp_raw_transaction_lib::RawTransactionWireField::Omitted,
|
||||||
|
);
|
||||||
|
if storm_sender
|
||||||
|
.send(crate::RawTransactionIngress {
|
||||||
|
material,
|
||||||
|
network: storm_network.clone(),
|
||||||
|
provenance: storm_provenance.clone(),
|
||||||
|
source_key: [61_u8; 32],
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
tokio::task::yield_now().await;
|
||||||
|
let peer_sender = sender.clone();
|
||||||
|
let peer_network = network.clone();
|
||||||
|
let peer_task = tokio::spawn(async move {
|
||||||
|
return peer_sender
|
||||||
|
.send(crate::RawTransactionIngress { material: peer_material, network: peer_network, provenance: peer_provenance, source_key: [99_u8; 32] })
|
||||||
|
.await
|
||||||
|
.is_ok();
|
||||||
|
});
|
||||||
|
tokio::task::yield_now().await;
|
||||||
|
assert_eq!(admission.queue_depth(), 1);
|
||||||
|
let first = match admission.receive(&network).await {
|
||||||
|
std::result::Result::Ok(std::option::Option::Some(value)) => value,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
assert_eq!(first.transaction().reference().signature().as_bytes(), &[60_u8; 64]);
|
||||||
|
let second = match admission.receive(&network).await {
|
||||||
|
std::result::Result::Ok(std::option::Option::Some(value)) => value,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
assert_eq!(second.transaction().reference().signature().as_bytes(), &[61_u8; 64]);
|
||||||
|
let third = match admission.receive(&network).await {
|
||||||
|
std::result::Result::Ok(std::option::Option::Some(value)) => value,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
assert_eq!(third.transaction().reference().signature().as_bytes(), &[99_u8; 64]);
|
||||||
|
for _ in 0..7 {
|
||||||
|
let next = admission.receive(&network).await;
|
||||||
|
assert!(matches!(next, std::result::Result::Ok(std::option::Option::Some(_))));
|
||||||
|
assert!(admission.queue_depth() <= 1);
|
||||||
|
}
|
||||||
|
let peer_joined = peer_task.await;
|
||||||
|
assert!(matches!(peer_joined, std::result::Result::Ok(true)));
|
||||||
|
let storm_joined = storm_task.await;
|
||||||
|
assert!(matches!(storm_joined, std::result::Result::Ok(true)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/persistence.rs
|
// file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/persistence.rs
|
||||||
// version: 1
|
// version: 3
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
enum PortResponse {
|
enum PortResponse {
|
||||||
@@ -12,6 +12,8 @@ struct FakePersistencePort {
|
|||||||
calls: std::sync::atomic::AtomicUsize,
|
calls: std::sync::atomic::AtomicUsize,
|
||||||
network: ksp_store_lib::RawNetworkId,
|
network: ksp_store_lib::RawNetworkId,
|
||||||
normal_mode_seen: std::sync::atomic::AtomicBool,
|
normal_mode_seen: std::sync::atomic::AtomicBool,
|
||||||
|
observation_calls: std::sync::atomic::AtomicUsize,
|
||||||
|
observation_response: std::option::Option<ksp_store_lib::RawObservationWriteOutcome>,
|
||||||
response: PortResponse,
|
response: PortResponse,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,9 +23,16 @@ impl FakePersistencePort {
|
|||||||
calls: std::sync::atomic::AtomicUsize::new(0),
|
calls: std::sync::atomic::AtomicUsize::new(0),
|
||||||
network,
|
network,
|
||||||
normal_mode_seen: std::sync::atomic::AtomicBool::new(false),
|
normal_mode_seen: std::sync::atomic::AtomicBool::new(false),
|
||||||
|
observation_calls: std::sync::atomic::AtomicUsize::new(0),
|
||||||
|
observation_response: std::option::Option::None,
|
||||||
response,
|
response,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn with_observation_response(mut self, observation_response: ksp_store_lib::RawObservationWriteOutcome) -> Self {
|
||||||
|
self.observation_response = std::option::Option::Some(observation_response);
|
||||||
|
return self;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl crate::RawTransactionIngestPersistencePort for FakePersistencePort {
|
impl crate::RawTransactionIngestPersistencePort for FakePersistencePort {
|
||||||
@@ -51,9 +60,50 @@ impl crate::RawTransactionIngestPersistencePort for FakePersistencePort {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn record_observation<'a>(
|
||||||
|
&'a self,
|
||||||
|
_observation: ksp_store_lib::RawTransactionObservation,
|
||||||
|
) -> ksp_store_lib::StoreApiFuture<'a, ksp_store_lib::Result<ksp_store_lib::RawObservationWriteOutcome>> {
|
||||||
|
self.observation_calls.fetch_add(1, std::sync::atomic::Ordering::AcqRel);
|
||||||
|
let observation_response = self.observation_response;
|
||||||
|
let response = self.response;
|
||||||
|
return std::boxed::Box::pin(async move {
|
||||||
|
if let std::option::Option::Some(outcome) = observation_response {
|
||||||
|
return std::result::Result::Ok(outcome);
|
||||||
|
}
|
||||||
|
return match response {
|
||||||
|
PortResponse::Outcome(_, observation) => std::result::Result::Ok(observation),
|
||||||
|
PortResponse::Conflict => std::result::Result::Err(ksp_core_lib::Error::new(ksp_store_lib::ERROR_CODE_RAW_CONFLICT, "synthetic conflict")),
|
||||||
|
PortResponse::StoreFailure => std::result::Result::Err(ksp_core_lib::Error::new(
|
||||||
|
ksp_core_lib::ErrorCode::new("synthetic_store", "write_failed"),
|
||||||
|
"synthetic remote failure text that must not escape",
|
||||||
|
)),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn acquisition(network_name: &str, signature_byte: u8) -> std::option::Option<ksp_raw_transaction_lib::RawTransactionAcquisition> {
|
fn acquisition(network_name: &str, signature_byte: u8) -> std::option::Option<ksp_raw_transaction_lib::RawTransactionAcquisition> {
|
||||||
|
return acquisition_with_observation_key(network_name, signature_byte, signature_byte);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn acquisition_with_observation_key(
|
||||||
|
network_name: &str,
|
||||||
|
signature_byte: u8,
|
||||||
|
observation_byte: u8,
|
||||||
|
) -> std::option::Option<ksp_raw_transaction_lib::RawTransactionAcquisition> {
|
||||||
|
return acquisition_with_shape(network_name, signature_byte, observation_byte, 42, std::option::Option::Some(1_700_000_000), "AQID");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn acquisition_with_shape(
|
||||||
|
network_name: &str,
|
||||||
|
signature_byte: u8,
|
||||||
|
observation_byte: u8,
|
||||||
|
slot: u64,
|
||||||
|
block_time: std::option::Option<i64>,
|
||||||
|
transaction_data: &str,
|
||||||
|
) -> std::option::Option<ksp_raw_transaction_lib::RawTransactionAcquisition> {
|
||||||
let network = match ksp_store_lib::RawNetworkId::new(network_name) {
|
let network = match ksp_store_lib::RawNetworkId::new(network_name) {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(_) => return std::option::Option::None,
|
std::result::Result::Err(_) => return std::option::Option::None,
|
||||||
@@ -62,9 +112,9 @@ fn acquisition(network_name: &str, signature_byte: u8) -> std::option::Option<ks
|
|||||||
let material = ksp_raw_transaction_lib::RawTransactionMaterial::binary_base64(
|
let material = ksp_raw_transaction_lib::RawTransactionMaterial::binary_base64(
|
||||||
network,
|
network,
|
||||||
signature,
|
signature,
|
||||||
42,
|
slot,
|
||||||
std::option::Option::Some(1_700_000_000),
|
block_time,
|
||||||
"AQID",
|
transaction_data,
|
||||||
ksp_raw_transaction_lib::RawTransactionWireField::Omitted,
|
ksp_raw_transaction_lib::RawTransactionWireField::Omitted,
|
||||||
ksp_raw_transaction_lib::RawTransactionWireField::Omitted,
|
ksp_raw_transaction_lib::RawTransactionWireField::Omitted,
|
||||||
ksp_raw_transaction_lib::RawTransactionWireField::Omitted,
|
ksp_raw_transaction_lib::RawTransactionWireField::Omitted,
|
||||||
@@ -92,7 +142,7 @@ fn acquisition(network_name: &str, signature_byte: u8) -> std::option::Option<ks
|
|||||||
let provenance = ksp_store_lib::RawAcquisitionProvenance::new(provider, protocol, method, ksp_store_lib::RawAcquisitionOrigin::Live, received_at);
|
let provenance = ksp_store_lib::RawAcquisitionProvenance::new(provider, protocol, method, ksp_store_lib::RawAcquisitionOrigin::Live, received_at);
|
||||||
return std::option::Option::Some(ksp_raw_transaction_lib::assemble_raw_transaction_acquisition(
|
return std::option::Option::Some(ksp_raw_transaction_lib::assemble_raw_transaction_acquisition(
|
||||||
transaction,
|
transaction,
|
||||||
ksp_store_lib::RawObservationKey::new([signature_byte; 32]),
|
ksp_store_lib::RawObservationKey::new([observation_byte; 32]),
|
||||||
provenance,
|
provenance,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -251,3 +301,108 @@ async fn pre_007_store_network_mismatch_is_rejected_before_write() {
|
|||||||
assert_eq!(port.calls.load(std::sync::atomic::Ordering::Acquire), 0);
|
assert_eq!(port.calls.load(std::sync::atomic::Ordering::Acquire), 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn v0_3_13_pre_008_same_canonical_identity_uses_atomic_entity_once_then_additional_observation() {
|
||||||
|
let network = match network("mainnet") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let port = FakePersistencePort::new(
|
||||||
|
network,
|
||||||
|
PortResponse::Outcome(ksp_store_lib::RawEntityWriteOutcome::Inserted, ksp_store_lib::RawObservationWriteOutcome::Inserted),
|
||||||
|
);
|
||||||
|
let convergence = crate::RawTransactionIngestPersistenceConvergence::new(8);
|
||||||
|
let first = match acquisition_with_observation_key("mainnet", 41, 1) {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let second = match acquisition_with_observation_key("mainnet", 41, 2) {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let first_outcome = crate::persist_raw_transaction_ingest_converged_acquisition(&port, first, &convergence).await;
|
||||||
|
let second_outcome = crate::persist_raw_transaction_ingest_converged_acquisition(&port, second, &convergence).await;
|
||||||
|
assert!(first_outcome.is_ok());
|
||||||
|
assert!(second_outcome.is_ok());
|
||||||
|
assert_eq!(port.calls.load(std::sync::atomic::Ordering::Acquire), 1);
|
||||||
|
assert_eq!(port.observation_calls.load(std::sync::atomic::Ordering::Acquire), 1);
|
||||||
|
let second_outcome = match second_outcome {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(second_outcome.entity(), crate::RawTransactionIngestEntityPersistence::AlreadyPresent);
|
||||||
|
assert_eq!(second_outcome.observation(), crate::RawTransactionIngestObservationPersistence::Inserted);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn v0_3_13_pre_008_replayed_same_observation_is_idempotent_without_raw_resubmission() {
|
||||||
|
let network = match network("mainnet") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let port = FakePersistencePort::new(
|
||||||
|
network,
|
||||||
|
PortResponse::Outcome(ksp_store_lib::RawEntityWriteOutcome::Inserted, ksp_store_lib::RawObservationWriteOutcome::Inserted),
|
||||||
|
)
|
||||||
|
.with_observation_response(ksp_store_lib::RawObservationWriteOutcome::AlreadyPresent);
|
||||||
|
let convergence = crate::RawTransactionIngestPersistenceConvergence::new(8);
|
||||||
|
let first = match acquisition_with_observation_key("mainnet", 42, 3) {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let second = match acquisition_with_observation_key("mainnet", 42, 3) {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let first_result = crate::persist_raw_transaction_ingest_converged_acquisition(&port, first, &convergence).await;
|
||||||
|
assert!(first_result.is_ok());
|
||||||
|
let second_result = crate::persist_raw_transaction_ingest_converged_acquisition(&port, second, &convergence).await;
|
||||||
|
let second_outcome = match second_result {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(port.calls.load(std::sync::atomic::Ordering::Acquire), 1);
|
||||||
|
assert_eq!(port.observation_calls.load(std::sync::atomic::Ordering::Acquire), 1);
|
||||||
|
assert_eq!(second_outcome.observation(), crate::RawTransactionIngestObservationPersistence::AlreadyPresent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn v0_3_13_pre_009_source_skew_and_provider_disagreement_fail_before_additional_observation() {
|
||||||
|
let cases = [
|
||||||
|
(43_u64, std::option::Option::Some(1_700_000_000_i64), "AQID"),
|
||||||
|
(42_u64, std::option::Option::Some(1_700_000_001_i64), "AQID"),
|
||||||
|
(42_u64, std::option::Option::Some(1_700_000_000_i64), "BAUG"),
|
||||||
|
];
|
||||||
|
for (index, (slot, block_time, transaction_data)) in cases.into_iter().enumerate() {
|
||||||
|
let network = match network("mainnet") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let port = FakePersistencePort::new(
|
||||||
|
network,
|
||||||
|
PortResponse::Outcome(ksp_store_lib::RawEntityWriteOutcome::Inserted, ksp_store_lib::RawObservationWriteOutcome::Inserted),
|
||||||
|
);
|
||||||
|
let convergence = crate::RawTransactionIngestPersistenceConvergence::new(8);
|
||||||
|
let first = match acquisition_with_shape("mainnet", 51, 10, 42, std::option::Option::Some(1_700_000_000), "AQID") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let divergent = match acquisition_with_shape("mainnet", 51, index as u8 + 20, slot, block_time, transaction_data) {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
assert!(crate::persist_raw_transaction_ingest_converged_acquisition(&port, first, &convergence).await.is_ok());
|
||||||
|
let conflict = crate::persist_raw_transaction_ingest_converged_acquisition(&port, divergent, &convergence).await;
|
||||||
|
let error = match conflict {
|
||||||
|
std::result::Result::Ok(_) => return,
|
||||||
|
std::result::Result::Err(value) => value,
|
||||||
|
};
|
||||||
|
assert_eq!(error.code(), crate::ERROR_CODE_RAW_TRANSACTION_INGEST_CONTENT_CONFLICT);
|
||||||
|
assert_eq!(port.calls.load(std::sync::atomic::Ordering::Acquire), 1);
|
||||||
|
assert_eq!(port.observation_calls.load(std::sync::atomic::Ordering::Acquire), 0);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime.rs
|
// file: crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime.rs
|
||||||
// version: 9
|
// version: 11
|
||||||
|
|
||||||
struct ActiveTaskGuard {
|
struct ActiveTaskGuard {
|
||||||
active: std::sync::Arc<std::sync::atomic::AtomicUsize>,
|
active: std::sync::Arc<std::sync::atomic::AtomicUsize>,
|
||||||
@@ -348,6 +348,36 @@ impl crate::RawTransactionIngestPersistencePort for RuntimePersistencePort {
|
|||||||
));
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn record_observation<'a>(
|
||||||
|
&'a self,
|
||||||
|
_observation: ksp_store_lib::RawTransactionObservation,
|
||||||
|
) -> ksp_store_lib::StoreApiFuture<'a, ksp_store_lib::Result<ksp_store_lib::RawObservationWriteOutcome>> {
|
||||||
|
let response = self.response;
|
||||||
|
return std::boxed::Box::pin(async move {
|
||||||
|
if matches!(response, RuntimePortResponse::StoreFailureBlocked | RuntimePortResponse::SuccessBlocked) {
|
||||||
|
let current = self.active.fetch_add(1, std::sync::atomic::Ordering::AcqRel) + 1;
|
||||||
|
let _active_guard = RuntimePortActiveGuard { active: &self.active };
|
||||||
|
self.update_max_active(current);
|
||||||
|
while !self.released.load(std::sync::atomic::Ordering::Acquire) {
|
||||||
|
self.notify.notified().await;
|
||||||
|
}
|
||||||
|
if matches!(response, RuntimePortResponse::SuccessBlocked) {
|
||||||
|
self.completed.fetch_add(1, std::sync::atomic::Ordering::AcqRel);
|
||||||
|
return std::result::Result::Ok(ksp_store_lib::RawObservationWriteOutcome::Inserted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if matches!(response, RuntimePortResponse::Conflict) {
|
||||||
|
self.completed.fetch_add(1, std::sync::atomic::Ordering::AcqRel);
|
||||||
|
return std::result::Result::Err(ksp_core_lib::Error::new(ksp_store_lib::ERROR_CODE_RAW_CONFLICT, "synthetic conflict"));
|
||||||
|
}
|
||||||
|
self.completed.fetch_add(1, std::sync::atomic::Ordering::AcqRel);
|
||||||
|
return std::result::Result::Err(ksp_core_lib::Error::new(
|
||||||
|
ksp_core_lib::ErrorCode::new("synthetic_store", "write_failed"),
|
||||||
|
"synthetic store failure",
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn runtime_ingress(network: &ksp_store_lib::RawNetworkId, signature_byte: u8) -> std::option::Option<crate::RawTransactionIngress> {
|
fn runtime_ingress(network: &ksp_store_lib::RawNetworkId, signature_byte: u8) -> std::option::Option<crate::RawTransactionIngress> {
|
||||||
@@ -869,3 +899,98 @@ async fn pre_009_drain_timeout_aborts_and_joins_all_owned_source_and_persistence
|
|||||||
assert_eq!(port.completed.load(std::sync::atomic::Ordering::Acquire), 0);
|
assert_eq!(port.completed.load(std::sync::atomic::Ordering::Acquire), 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn v0_3_13_pre_011_source_counter_exhaustion_stays_terminal_and_is_not_collapsed_to_source_failed() {
|
||||||
|
let settings = match settings("mainnet") {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let handle = match super::start_foundation_with_source_spawner(
|
||||||
|
settings,
|
||||||
|
tokio::runtime::Handle::current(),
|
||||||
|
std::option::Option::None,
|
||||||
|
move |children: &mut tokio::task::JoinSet<ksp_core_lib::Result<()>>, _stop_receiver, _admission_sender| {
|
||||||
|
let _abort_handle = children.spawn(async move {
|
||||||
|
return std::result::Result::Err(crate::counter_exhausted_error("source_reconnect_total"));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let source = handle.snapshot_source();
|
||||||
|
let terminal = match handle.wait_terminal().await {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(terminal, ksp_worker_api::WorkerState::Faulted(crate::ERROR_CODE_RAW_TRANSACTION_INGEST_COUNTER_EXHAUSTED));
|
||||||
|
let snapshot = source.current();
|
||||||
|
assert_eq!(snapshot.source_failure_total(), 1);
|
||||||
|
assert_eq!(snapshot.worker_snapshot().state(), terminal);
|
||||||
|
assert_eq!(snapshot.worker_snapshot().health(), ksp_worker_api::WorkerHealth::Unhealthy);
|
||||||
|
let terminal_sequence = snapshot.worker_snapshot().sequence();
|
||||||
|
assert!(!handle.request_stop());
|
||||||
|
let retained = source.current();
|
||||||
|
assert_eq!(retained.worker_snapshot().state(), terminal);
|
||||||
|
assert_eq!(retained.worker_snapshot().sequence(), terminal_sequence);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "current_thread")]
|
||||||
|
async fn v0_3_13_pre_011_drain_timeout_prevents_late_persistence_completion_after_terminal() {
|
||||||
|
let settings = match settings_with_runtime_limits(1, 1, crate::MIN_RAW_TRANSACTION_INGEST_SHUTDOWN_DRAIN_TIMEOUT) {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let network = settings.network().clone();
|
||||||
|
let port = std::sync::Arc::new(RuntimePersistencePort::new(network.clone(), RuntimePortResponse::SuccessBlocked, false));
|
||||||
|
let runtime_port: super::PersistencePort = port.clone();
|
||||||
|
let source_active = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
|
||||||
|
let source_active_for_task = std::sync::Arc::clone(&source_active);
|
||||||
|
let handle = match super::start_foundation_with_port_and_source_spawner(
|
||||||
|
settings,
|
||||||
|
tokio::runtime::Handle::current(),
|
||||||
|
std::option::Option::Some(runtime_port),
|
||||||
|
move |children, _stop_receiver, admission_sender| {
|
||||||
|
let _abort_handle = children.spawn(async move {
|
||||||
|
let ingress = match runtime_ingress(&network, 81) {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return std::result::Result::Err(crate::runtime_error("test.ingress_invalid")),
|
||||||
|
};
|
||||||
|
if admission_sender.send(ingress).await.is_err() {
|
||||||
|
return std::result::Result::Err(crate::runtime_error("test.source_failed"));
|
||||||
|
}
|
||||||
|
let _guard = ActiveTaskGuard::new(source_active_for_task);
|
||||||
|
return std::future::pending::<ksp_core_lib::Result<()>>().await;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert!(wait_for_max_active(&port, 1).await);
|
||||||
|
assert!(wait_for_active_count(&source_active, 1).await);
|
||||||
|
assert!(handle.request_stop());
|
||||||
|
let terminal = match handle.wait_terminal().await {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(terminal, ksp_worker_api::WorkerState::Faulted(crate::ERROR_CODE_RAW_TRANSACTION_INGEST_DRAIN_TIMEOUT));
|
||||||
|
let snapshot = handle.snapshot_source().current();
|
||||||
|
let terminal_sequence = snapshot.worker_snapshot().sequence();
|
||||||
|
assert_eq!(snapshot.in_flight_persistence(), 0);
|
||||||
|
assert_eq!(port.active.load(std::sync::atomic::Ordering::Acquire), 0);
|
||||||
|
assert_eq!(port.completed.load(std::sync::atomic::Ordering::Acquire), 0);
|
||||||
|
assert_eq!(source_active.load(std::sync::atomic::Ordering::Acquire), 0);
|
||||||
|
port.release();
|
||||||
|
for _ in 0..64 {
|
||||||
|
tokio::task::yield_now().await;
|
||||||
|
}
|
||||||
|
assert_eq!(port.completed.load(std::sync::atomic::Ordering::Acquire), 0);
|
||||||
|
let retained = handle.snapshot_source().current();
|
||||||
|
assert_eq!(retained.worker_snapshot().state(), terminal);
|
||||||
|
assert_eq!(retained.worker_snapshot().sequence(), terminal_sequence);
|
||||||
|
assert_eq!(retained.in_flight_persistence(), 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,9 @@
|
|||||||
// 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: 4
|
// version: 7
|
||||||
|
|
||||||
fn snapshot_foundation() -> std::option::Option<(crate::RawTransactionIngestSnapshotPublisher, crate::RawTransactionIngestSnapshotSource)> {
|
fn snapshot_foundation_with_source_total(
|
||||||
|
source_total: usize,
|
||||||
|
) -> std::option::Option<(crate::RawTransactionIngestSnapshotPublisher, crate::RawTransactionIngestSnapshotSource)> {
|
||||||
let network = match ksp_store_lib::RawNetworkId::new("mainnet") {
|
let network = match ksp_store_lib::RawNetworkId::new("mainnet") {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(_) => return std::option::Option::None,
|
std::result::Result::Err(_) => return std::option::Option::None,
|
||||||
@@ -19,7 +21,11 @@ fn snapshot_foundation() -> std::option::Option<(crate::RawTransactionIngestSnap
|
|||||||
if lifecycle.start().is_err() {
|
if lifecycle.start().is_err() {
|
||||||
return std::option::Option::None;
|
return std::option::Option::None;
|
||||||
}
|
}
|
||||||
return std::option::Option::Some(crate::RawTransactionIngestSnapshotPublisher::new(&settings, &lifecycle));
|
return std::option::Option::Some(crate::RawTransactionIngestSnapshotPublisher::new(&settings, &lifecycle, source_total));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn snapshot_foundation() -> std::option::Option<(crate::RawTransactionIngestSnapshotPublisher, crate::RawTransactionIngestSnapshotSource)> {
|
||||||
|
return snapshot_foundation_with_source_total(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -54,6 +60,10 @@ fn pre_008_initial_snapshot_and_common_projection_are_exact() {
|
|||||||
assert_eq!(concrete.processing_frontier_slot(), std::option::Option::None);
|
assert_eq!(concrete.processing_frontier_slot(), std::option::Option::None);
|
||||||
assert_eq!(concrete.oldest_pending_slot(), std::option::Option::None);
|
assert_eq!(concrete.oldest_pending_slot(), std::option::Option::None);
|
||||||
assert_eq!(concrete.source_state(), std::option::Option::None);
|
assert_eq!(concrete.source_state(), std::option::Option::None);
|
||||||
|
assert_eq!(concrete.source_total(), 0);
|
||||||
|
assert_eq!(concrete.source_active(), 0);
|
||||||
|
assert_eq!(concrete.source_reconnecting(), 0);
|
||||||
|
assert_eq!(concrete.source_failed(), 0);
|
||||||
assert_eq!(concrete.source_reconnect_total(), 0);
|
assert_eq!(concrete.source_reconnect_total(), 0);
|
||||||
assert_eq!(concrete.source_replay_attempt_total(), 0);
|
assert_eq!(concrete.source_replay_attempt_total(), 0);
|
||||||
assert_eq!(concrete.source_continuity_gap_total(), 0);
|
assert_eq!(concrete.source_continuity_gap_total(), 0);
|
||||||
@@ -199,3 +209,140 @@ fn pre_008_source_continuity_projection_preserves_processing_frontier_and_distin
|
|||||||
assert_eq!(resumed.worker_snapshot().activity(), ksp_worker_api::WorkerActivity::Idle);
|
assert_eq!(resumed.worker_snapshot().activity(), ksp_worker_api::WorkerActivity::Idle);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn v0_3_13_pre_010_multi_source_counts_and_health_are_conservative_and_source_neutral() {
|
||||||
|
let (mut publisher, source) = match snapshot_foundation_with_source_total(3) {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => return,
|
||||||
|
};
|
||||||
|
let initial = source.current();
|
||||||
|
assert_eq!(initial.source_total(), 3);
|
||||||
|
assert_eq!(initial.source_active(), 0);
|
||||||
|
assert_eq!(initial.source_reconnecting(), 0);
|
||||||
|
assert_eq!(initial.source_failed(), 0);
|
||||||
|
assert_eq!(initial.worker_snapshot().health(), ksp_worker_api::WorkerHealth::Unknown);
|
||||||
|
let reconnecting = crate::RawTransactionIngestProcessingFrontierProjection::new(1, std::option::Option::Some(50), std::option::Option::Some(50))
|
||||||
|
.with_source_continuity(std::option::Option::Some(crate::RawTransactionIngestSourceState::Reconnecting), 0, 1, 0)
|
||||||
|
.with_source_counts(3, 2, 1, 0);
|
||||||
|
assert!(publisher.record_processing_frontier(ksp_worker_api::WorkerState::Running, 0, 0, reconnecting).is_ok());
|
||||||
|
let degraded = source.current();
|
||||||
|
assert_eq!(degraded.source_total(), 3);
|
||||||
|
assert_eq!(degraded.source_active(), 2);
|
||||||
|
assert_eq!(degraded.source_reconnecting(), 1);
|
||||||
|
assert_eq!(degraded.source_failed(), 0);
|
||||||
|
assert_eq!(degraded.worker_snapshot().health(), ksp_worker_api::WorkerHealth::Degraded);
|
||||||
|
assert_eq!(degraded.worker_snapshot().activity(), ksp_worker_api::WorkerActivity::Active);
|
||||||
|
let active = crate::RawTransactionIngestProcessingFrontierProjection::new(0, std::option::Option::Some(51), std::option::Option::None)
|
||||||
|
.with_source_continuity(std::option::Option::Some(crate::RawTransactionIngestSourceState::Active), 1, 1, 0)
|
||||||
|
.with_source_counts(3, 3, 0, 0);
|
||||||
|
assert!(publisher.record_processing_frontier(ksp_worker_api::WorkerState::Running, 0, 0, active).is_ok());
|
||||||
|
let healthy = source.current();
|
||||||
|
assert_eq!(healthy.worker_snapshot().health(), ksp_worker_api::WorkerHealth::Healthy);
|
||||||
|
assert_eq!(healthy.worker_snapshot().activity(), ksp_worker_api::WorkerActivity::Idle);
|
||||||
|
let failed = crate::RawTransactionIngestProcessingFrontierProjection::new(0, std::option::Option::Some(51), std::option::Option::None)
|
||||||
|
.with_source_continuity(std::option::Option::Some(crate::RawTransactionIngestSourceState::Failed), 1, 1, 0)
|
||||||
|
.with_source_counts(3, 2, 0, 1);
|
||||||
|
assert!(publisher.record_processing_frontier(ksp_worker_api::WorkerState::Running, 0, 0, failed).is_ok());
|
||||||
|
let unhealthy = source.current();
|
||||||
|
assert_eq!(unhealthy.source_failed(), 1);
|
||||||
|
assert_eq!(unhealthy.worker_snapshot().health(), ksp_worker_api::WorkerHealth::Unhealthy);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|||||||
274
deltas/0.3.13/pre.001.md
Normal file
274
deltas/0.3.13/pre.001.md
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
<!-- file: deltas/0.3.13/pre.001.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta 0.3.13-pre.001 — audit règles et convergence live multi-source
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
archive : khadhroony-solana-project-v0.3.12.zip
|
||||||
|
SHA-256 : 85c41eb9f8575ee58edd9863445cbc37b79ef59c4a7cf882679ff24dc8991bf5
|
||||||
|
ZIP bytes : 8260528
|
||||||
|
ZIP entries : 1981
|
||||||
|
workspace.package.version base : 0.3.12
|
||||||
|
stable delta : deltas/0.3.12/rel.001.md
|
||||||
|
prompt : prompts/032-V0_3_13_START_PROMPT.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Exécuter le gate `pre.001` de `0.3.13` avant tout développement : vérifier l'archive stable et les règles, réauditer les surfaces WS standard/Helius/HTTP actuelles, fermer l'architecture de convergence multi-source, les bornes, la stratégie d'observations multiples, le content conflict, les owners retry/reconnect, les smokes et le sizing.
|
||||||
|
|
||||||
|
Aucune nouvelle source réseau productive n'est implémentée dans cette tranche.
|
||||||
|
|
||||||
|
## Type de livraison
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.13-pre.001
|
||||||
|
Cargo : 0.3.13-pre.1
|
||||||
|
archive : ksp-general-0.3.13-pre.001.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Le type `general` est requis par la synchronisation de `Cargo.toml` pour une prerelease non-fix.
|
||||||
|
|
||||||
|
## Vérification archive
|
||||||
|
|
||||||
|
Contrôlé :
|
||||||
|
|
||||||
|
```text
|
||||||
|
unzip -t : PASS
|
||||||
|
absolute entries : 0
|
||||||
|
parent traversal : 0
|
||||||
|
duplicate ZIP entries : 0
|
||||||
|
symlink entries : 0
|
||||||
|
Cargo.lock : absent
|
||||||
|
rust-toolchain.toml : absent
|
||||||
|
package lockfiles : absents
|
||||||
|
.env : absent
|
||||||
|
crate manifests : 21
|
||||||
|
workspace members : 21
|
||||||
|
crates hors workspace : 0
|
||||||
|
members sans crate : 0
|
||||||
|
workspace edition : 2024
|
||||||
|
```
|
||||||
|
|
||||||
|
## Audit règles
|
||||||
|
|
||||||
|
Les règles actives ont été relues depuis `RULES.md` et les neuf documents de `docs/rules/`.
|
||||||
|
|
||||||
|
Le script Rust stable reste vert, mais un contrôle supplémentaire d'unicité des identifiants normatifs a trouvé :
|
||||||
|
|
||||||
|
```text
|
||||||
|
rule ids : 489
|
||||||
|
unique ids : 488
|
||||||
|
collision : KSP-CONFIG-018 x2
|
||||||
|
```
|
||||||
|
|
||||||
|
La preuve historique `deltas/0.1.3/pre.014.md` attribue explicitement `KSP-CONFIG-018` à la règle d'inventaire `.env.example`. La règle desktop plus récente est donc renumérotée `KSP-CONFIG-019` sans modification de sa sémantique.
|
||||||
|
|
||||||
|
Après correction :
|
||||||
|
|
||||||
|
```text
|
||||||
|
rule ids : 489
|
||||||
|
unique ids : 489
|
||||||
|
duplicates : 0
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune autre contradiction normative active n'a été identifiée.
|
||||||
|
|
||||||
|
## Audit architecture/dépendances
|
||||||
|
|
||||||
|
Confirmé :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Worker -> Transport autorisé et déjà productif
|
||||||
|
Worker -> Config interdit et absent
|
||||||
|
Worker -> Job Backfill interdit et absent
|
||||||
|
Worker -> backend Store physique interdit et absent
|
||||||
|
Worker -> reqwest/tonic/yellowstone proto direct absent
|
||||||
|
Transport -> Store absent
|
||||||
|
Transport -> Common RAW absent
|
||||||
|
ksp-store-lib reste la façade de persistence Worker
|
||||||
|
Common RAW reste propriétaire de la canonicalisation
|
||||||
|
```
|
||||||
|
|
||||||
|
`solana-keypair` dans `ksp-wallet-lib` est explicitement admis par `DEP-SOL-004` et ne constitue pas une violation.
|
||||||
|
|
||||||
|
## Audit WS / Helius / HTTP courant
|
||||||
|
|
||||||
|
Décisions :
|
||||||
|
|
||||||
|
```text
|
||||||
|
logsSubscribe standard -> signature/slot -> getTransaction observed
|
||||||
|
blockSubscribe standard -> full/base64, maxSupportedTransactionVersion=1
|
||||||
|
Helius transactionSubscribe -> full/base64 -> getTransaction observed
|
||||||
|
Helius LaserStream WSS ne porte pas blockSubscribe
|
||||||
|
HTTP live -> getSlot + getBlocksWithLimit + getBlock observed
|
||||||
|
```
|
||||||
|
|
||||||
|
La documentation Solana courante demande la préparation `maxSupportedTransactionVersion = 1` pour Transaction V1. V1 n'est pas encore actif sur les clusters publics au 10 septembre 2026, donc la voie `blockSubscribe` V1 reste deterministic-test-gated avant d'être déclarée RAW-direct.
|
||||||
|
|
||||||
|
Le pricing Helius courant place `transactionSubscribe` à partir du tier Developer ; ce fait reste une qualification de smoke, jamais une constante métier KSP.
|
||||||
|
|
||||||
|
## Architecture multi-source fermée
|
||||||
|
|
||||||
|
Décisions principales :
|
||||||
|
|
||||||
|
```text
|
||||||
|
sources simultanées, pas de first-provider-wins
|
||||||
|
source identity opaque SHA-256 et provider-neutral
|
||||||
|
1..32 sources runtime
|
||||||
|
collection validée avant tout spawn
|
||||||
|
coordinator global Worker avant hydration
|
||||||
|
coalescence (network, signature, commitment)
|
||||||
|
une hydration HTTP pour les références cross-source identiques
|
||||||
|
pending global <= admission_queue_capacity effective
|
||||||
|
hydratation in-flight <= persistence_concurrency
|
||||||
|
aucune queue Worker provider privée non bornée
|
||||||
|
```
|
||||||
|
|
||||||
|
Observation/persistence :
|
||||||
|
|
||||||
|
```text
|
||||||
|
identité durable = network + signature
|
||||||
|
première observation = persist_raw_transaction_acquisition atomique
|
||||||
|
observations supplémentaires = record_raw_transaction_observation
|
||||||
|
même identité + même contenu = idempotence
|
||||||
|
même identité + contenu divergent = content conflict terminal
|
||||||
|
```
|
||||||
|
|
||||||
|
La capability Store d'observation additionnelle existe déjà ; aucun changement Store API n'est requis.
|
||||||
|
|
||||||
|
## HTTP live polling
|
||||||
|
|
||||||
|
Contrat de plan retenu :
|
||||||
|
|
||||||
|
```text
|
||||||
|
run start = getSlot(commitment)
|
||||||
|
aucun scan avant ce start slot
|
||||||
|
getBlocksWithLimit pour exclure naturellement skipped slots
|
||||||
|
getBlock observed pour chaque bloc listé
|
||||||
|
poll interval : 100 ms..30 s, default 1 s
|
||||||
|
blocks/cycle : 1..1024, default 128
|
||||||
|
timer/cadence : Worker
|
||||||
|
retry/rate-limit HTTP : Transport
|
||||||
|
```
|
||||||
|
|
||||||
|
## Health et continuité
|
||||||
|
|
||||||
|
Le Worker conservera un inventaire de source privé et borné. La surface publique restera source-neutral avec des counts agrégés seulement.
|
||||||
|
|
||||||
|
Politique `0.3.13` conservative : une source configurée qui devient terminalement `Failed` faute le Worker, car l'équivalence de coverage des autres sources n'est pas prouvée. Le failover non-terminal et le repair appartiennent à `0.3.14`.
|
||||||
|
|
||||||
|
## Sizing retenu
|
||||||
|
|
||||||
|
```text
|
||||||
|
pre.002 runtime resources multi-source
|
||||||
|
pre.003 standard logs + hydration
|
||||||
|
pre.004 standard blockSubscribe
|
||||||
|
pre.005 Helius transactionSubscribe
|
||||||
|
pre.006 HTTP live polling
|
||||||
|
pre.007 supervisor/source inventory
|
||||||
|
pre.008 convergence/observations multiples
|
||||||
|
pre.009 conflict/backpressure/fairness
|
||||||
|
pre.010 snapshots/health
|
||||||
|
pre.011 races/shutdown
|
||||||
|
pre.012 completeness/security
|
||||||
|
pre.013 gate technique/live
|
||||||
|
pre.014 réconciliation documentaire
|
||||||
|
pre.015 préparation publication
|
||||||
|
rel.001 stable
|
||||||
|
```
|
||||||
|
|
||||||
|
Décision : `0.3.13` est maintenue ; aucune rescission n'est nécessaire avant `pre.002`.
|
||||||
|
|
||||||
|
## Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/plans/034-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE_PLAN.md
|
||||||
|
docs/validation/030-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE.md
|
||||||
|
deltas/0.3.13/pre.001.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
docs/rules/RULES_KSP.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers supprimés
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validations exécutées avant modification
|
||||||
|
|
||||||
|
```text
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py
|
||||||
|
PASS : General Rust rule audit clean
|
||||||
|
PASS : Rust export completeness audit 0 candidate
|
||||||
|
PASS : KSP workspace Rust rule audit clean
|
||||||
|
|
||||||
|
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.3.12
|
||||||
|
PASS : Markdown table audit clean (306 tables, 199 files)
|
||||||
|
```
|
||||||
|
|
||||||
|
La preuve Cargo stable fournie par l'opérateur montre également fmt/check/clippy/tests Worker/trees verts sur `0.3.12`; elle reste une preuve de la base et non une exécution locale de cette prerelease.
|
||||||
|
|
||||||
|
## Validations exécutées après modification
|
||||||
|
|
||||||
|
```text
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py
|
||||||
|
PASS : General Rust rule audit clean
|
||||||
|
PASS : Rust export completeness audit 0 candidate
|
||||||
|
PASS : KSP workspace Rust rule audit clean
|
||||||
|
|
||||||
|
supplemental normative rule-id scan
|
||||||
|
PASS : 489 ids, 489 uniques, 0 duplicate
|
||||||
|
|
||||||
|
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas
|
||||||
|
PASS : Markdown table audit clean (340 tables, 828 files)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validations non exécutées localement
|
||||||
|
|
||||||
|
Le toolchain Cargo/Rustfmt n'est pas installé dans l'environnement d'assemblage :
|
||||||
|
|
||||||
|
```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
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
Statut : `NON EXÉCUTÉ LOCAL`, jamais PASS.
|
||||||
|
|
||||||
|
## Décisions prises
|
||||||
|
|
||||||
|
```text
|
||||||
|
corriger l'identifiant desktop en KSP-CONFIG-019
|
||||||
|
maintenir la release 0.3.13
|
||||||
|
aucune nouvelle dépendance externe
|
||||||
|
maxSupportedTransactionVersion=1 pour les nouvelles voies concernées
|
||||||
|
convergence/hydration globale Worker-owned
|
||||||
|
observation fanout via capability Store existante
|
||||||
|
source failure terminale conservative jusqu'au coverage/repair 0.3.14
|
||||||
|
```
|
||||||
|
|
||||||
|
## Questions ouvertes
|
||||||
|
|
||||||
|
Reportées explicitement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
coverage équivalent entre sources
|
||||||
|
failover non-terminal
|
||||||
|
repair de gaps multi-source
|
||||||
|
sources EARLY
|
||||||
|
campagnes historiques multi-source
|
||||||
|
composition Desk 0.3.15
|
||||||
|
```
|
||||||
171
deltas/0.3.13/pre.002-fix.001.md
Normal file
171
deltas/0.3.13/pre.002-fix.001.md
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
<!-- file: deltas/0.3.13/pre.002-fix.001.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta 0.3.13-pre.002-fix.001 — normalisation rustfmt du root Worker
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
delivery précédente : 0.3.13-pre.002
|
||||||
|
Cargo base : 0.3.13-pre.2
|
||||||
|
delta base : deltas/0.3.13/pre.002.md
|
||||||
|
archive delta base : ksp-general-0.3.13-pre.002.zip
|
||||||
|
SHA-256 delta base : 9e7c65d2340fe4918fcfa285cc14beabe4cccd3af1a58aa86e1d24a7ac8bf7b2
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur exécuté après application de `pre.002` a identifié un seul défaut : `cargo fmt --all -- --check` demande le tri de l'export `MAX_RAW_TRANSACTION_INGEST_LIVE_SOURCES` après les exports du module `runtime`. Les audits Rust/Markdown, `cargo check --workspace`, Clippy strict et les suites Transport/Worker exécutées ensuite sont verts.
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Corriger strictement le défaut de format de `pre.002`, sans modification fonctionnelle :
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucune API ajoutée ou supprimée
|
||||||
|
aucune signature modifiée
|
||||||
|
aucune dépendance modifiée
|
||||||
|
aucune logique runtime modifiée
|
||||||
|
aucun changement du plan pre.003+
|
||||||
|
réordonnancement rustfmt uniquement dans le root Worker
|
||||||
|
```
|
||||||
|
|
||||||
|
## Type de livraison
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison : 0.3.13-pre.002-fix.001
|
||||||
|
Cargo : 0.3.13-pre.2.fix.1
|
||||||
|
archive : ksp-general-0.3.13-pre.002-fix.001.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Conformément à `VER-ID-007` et `VER-ID-010`, le correctif touche un fichier `.rs`; `workspace.package.version` est donc synchronisé avec l'identifiant SemVer technique du fix.
|
||||||
|
|
||||||
|
## Correction
|
||||||
|
|
||||||
|
Dans `crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs`, l'export :
|
||||||
|
|
||||||
|
```text
|
||||||
|
MAX_RAW_TRANSACTION_INGEST_LIVE_SOURCES
|
||||||
|
```
|
||||||
|
|
||||||
|
est déplacé après le groupe des exports `runtime`, exactement comme demandé par `rustfmt` dans le gate opérateur.
|
||||||
|
|
||||||
|
L'ordre corrigé est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RAW_TRANSACTION_INGEST_WORKER_KIND_CODE
|
||||||
|
RawTransactionIngestHandle
|
||||||
|
RawTransactionIngestTerminalFuture
|
||||||
|
RawTransactionIngestWorker
|
||||||
|
MAX_RAW_TRANSACTION_INGEST_LIVE_SOURCES
|
||||||
|
RawTransactionIngestRuntimeResources
|
||||||
|
RawTransactionIngestYellowstoneSource
|
||||||
|
```
|
||||||
|
|
||||||
|
Le changement est purement lexical. La constante, sa visibilité, son chemin public et sa valeur restent inchangés.
|
||||||
|
|
||||||
|
## Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.3.13/pre.002-fix.001.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers supprimés
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun
|
||||||
|
```
|
||||||
|
|
||||||
|
## Versions de fichiers incrémentées
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml : 540 -> 541
|
||||||
|
worker lib.rs : 18 -> 19
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validations exécutées sur la base pre.002 par l'opérateur
|
||||||
|
|
||||||
|
Le log opérateur fourni pour `0.3.13-pre.2` confirme :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all -- --check : FAIL, uniquement diff de tri d'exports dans worker src/lib.rs
|
||||||
|
General Rust rule audit : clean
|
||||||
|
Rust export completeness audit : 0 candidate(s)
|
||||||
|
KSP workspace Rust rule audit : clean
|
||||||
|
Markdown table audit : clean (340 tables, 829 files)
|
||||||
|
cargo check --workspace : PASS
|
||||||
|
cargo clippy --workspace --all-targets --all-features -- -D warnings : PASS
|
||||||
|
cargo test -p ksp-onchain-transport-lib : PASS
|
||||||
|
cargo test -p ksp-worker-raw-transaction-ingest-lib : PASS
|
||||||
|
```
|
||||||
|
|
||||||
|
Suites explicitement observées :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Transport unit : 389 passed
|
||||||
|
Transport public_api : 52 passed
|
||||||
|
Transport release_completeness : 44 passed
|
||||||
|
Transport doc-tests : 4 passed
|
||||||
|
smokes réseau : ignored / opt-in conformément au contrat
|
||||||
|
Worker unit : 70 passed
|
||||||
|
Worker cross_layer_completeness : 4 passed
|
||||||
|
Worker dependency_boundary : 9 passed
|
||||||
|
Worker hardening : 16 passed
|
||||||
|
Worker public_api : 10 passed
|
||||||
|
Worker release_completeness : 4 passed
|
||||||
|
Worker doc-tests : 0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validations exécutées dans l'environnement d'assemblage du fix
|
||||||
|
|
||||||
|
```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
|
||||||
|
contrôle supplémentaire des collisions d'identifiants normatifs
|
||||||
|
contrôle exhaustif du diff pre.002 -> pre.002-fix.001
|
||||||
|
contrôle du contenu et de l'intégrité ZIP
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validations non exécutées localement
|
||||||
|
|
||||||
|
Le toolchain Rust n'est pas disponible dans l'environnement d'assemblage. Les commandes suivantes ne sont donc pas déclarées PASS localement :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all -- --check
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||||
|
cargo test -p ksp-onchain-transport-lib
|
||||||
|
cargo test -p ksp-worker-raw-transaction-ingest-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
Le correctif reproduit exactement le déplacement demandé par le diff `cargo fmt --all -- --check` du gate opérateur.
|
||||||
|
|
||||||
|
## Gate opérateur avant pre.003
|
||||||
|
|
||||||
|
```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
|
||||||
|
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||||
|
cargo test -p ksp-onchain-transport-lib
|
||||||
|
cargo test -p ksp-worker-raw-transaction-ingest-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
## Décisions
|
||||||
|
|
||||||
|
- Le fix ne change pas le périmètre fonctionnel de `pre.002`.
|
||||||
|
- Le supervisor multi-source reste réservé à `pre.007`.
|
||||||
|
- `pre.003` reste la prochaine tranche fonctionnelle après validation de ce correctif.
|
||||||
|
- Aucun document durable n'est modifié pour une correction de format déjà entièrement décrite par ce delta.
|
||||||
|
|
||||||
|
## Questions ouvertes
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucune
|
||||||
|
```
|
||||||
323
deltas/0.3.13/pre.002.md
Normal file
323
deltas/0.3.13/pre.002.md
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
<!-- file: deltas/0.3.13/pre.002.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta 0.3.13-pre.002 — runtime resources multi-source bornées
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
delivery précédente : 0.3.13-pre.001
|
||||||
|
Cargo base : 0.3.13-pre.1
|
||||||
|
delta base : deltas/0.3.13/pre.001.md
|
||||||
|
archive delta base : ksp-general-0.3.13-pre.001.zip
|
||||||
|
SHA-256 delta base : ca57546f19bd47b5d3b832a51905159497a5e601c186460db22b04364a1835c4
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur fourni après application de `pre.001` est vert : fmt, audits Rust/Markdown, check workspace, Clippy strict, tests Worker, arbres normal/features et duplicates ont été exécutés sans anomalie signalée.
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Matérialiser le contrat `pre.002` défini par le plan `034` sans démarrer prématurément les nouvelles familles live :
|
||||||
|
|
||||||
|
```text
|
||||||
|
collection runtime privée bornée 1..32
|
||||||
|
identité logique déterministe par source
|
||||||
|
source discriminant capability-owned
|
||||||
|
validation globale avant spawn
|
||||||
|
rejet des doublons logiques
|
||||||
|
rejet cross-network
|
||||||
|
aucun nouveau client réseau
|
||||||
|
aucune nouvelle dépendance
|
||||||
|
```
|
||||||
|
|
||||||
|
Le supervisor simultané appartient toujours à `pre.007`.
|
||||||
|
|
||||||
|
## Type de livraison
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.13-pre.002
|
||||||
|
Cargo : 0.3.13-pre.2
|
||||||
|
archive : ksp-general-0.3.13-pre.002.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
## Runtime resources multi-source
|
||||||
|
|
||||||
|
`RawTransactionIngestRuntimeResources` ne contient plus directement un unique champ Yellowstone. Il possède désormais une collection privée :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Vec<RawTransactionIngestLiveSource>
|
||||||
|
```
|
||||||
|
|
||||||
|
Le discriminant privé initial est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Yellowstone(RawTransactionIngestYellowstoneSource)
|
||||||
|
```
|
||||||
|
|
||||||
|
Il est volontairement capability-owned ; aucun provider n'entre dans l'enum Worker.
|
||||||
|
|
||||||
|
Surface publique ajoutée :
|
||||||
|
|
||||||
|
```text
|
||||||
|
MAX_RAW_TRANSACTION_INGEST_LIVE_SOURCES = 32
|
||||||
|
RawTransactionIngestRuntimeResources::source_count()
|
||||||
|
RawTransactionIngestRuntimeResources::try_push_yellowstone_source(...)
|
||||||
|
```
|
||||||
|
|
||||||
|
`try_push_yellowstone_source` est transactionnel : le candidat est entièrement rejeté sans mutation de la collection si la borne, le réseau ou l'identité logique sont invalides.
|
||||||
|
|
||||||
|
## Identité logique Yellowstone
|
||||||
|
|
||||||
|
Chaque `RawTransactionIngestYellowstoneSource` possède maintenant un `source_key: [u8; 32]` privé.
|
||||||
|
|
||||||
|
Le hash SHA-256 est dérivé uniquement de données sûres :
|
||||||
|
|
||||||
|
```text
|
||||||
|
KSP domain separator
|
||||||
|
source family = yellowstone
|
||||||
|
network
|
||||||
|
provider identity sûre
|
||||||
|
endpoint identity sûre
|
||||||
|
commitment
|
||||||
|
identité opaque de YellowstoneSubscribeRequest
|
||||||
|
```
|
||||||
|
|
||||||
|
Le rôle et le pool HTTP d'hydration sont volontairement exclus du `source_key` : ils ne définissent pas une nouvelle source live Yellowstone. Ainsi, deux subscriptions identiques ne deviennent pas artificiellement distinctes par simple changement de stratégie d'hydration.
|
||||||
|
|
||||||
|
Sont explicitement absents du matériau en clair et de Debug :
|
||||||
|
|
||||||
|
```text
|
||||||
|
URL
|
||||||
|
API key / credential
|
||||||
|
header
|
||||||
|
transaction payload
|
||||||
|
remote error material
|
||||||
|
request identity bytes
|
||||||
|
source_key bytes/hash
|
||||||
|
```
|
||||||
|
|
||||||
|
Concernant `source_key`, le Debug de la source n'en publie que la longueur ; ses autres champs Debug restent les identités sûres déjà admises par le contrat stable. Le Debug de l'aggregate runtime publie seulement `source_count`.
|
||||||
|
|
||||||
|
## Identité déterministe de SubscribeRequest dans Transport
|
||||||
|
|
||||||
|
`ksp-onchain-transport-lib` expose `YellowstoneSubscribeRequestIdentity` comme valeur opaque et `YellowstoneSubscribeRequest::identity()`.
|
||||||
|
|
||||||
|
Canonicalisation de l'identité :
|
||||||
|
|
||||||
|
```text
|
||||||
|
sept familles de filtres séparées
|
||||||
|
ordre déterministe fourni par les BTreeMap KSP
|
||||||
|
nom de filtre length-framed
|
||||||
|
protobuf exact de chaque filtre length-framed
|
||||||
|
options communes protobuf encodées après vidage des sept maps
|
||||||
|
```
|
||||||
|
|
||||||
|
Les bytes internes restent privés. L'identité implémente `Hash` afin que le Worker puisse l'incorporer à son SHA-256 sans escape hatch de sérialisation.
|
||||||
|
|
||||||
|
Aucune dépendance n'est ajoutée : le codage réutilise `prost` réexporté par `yellowstone-grpc-proto`, déjà dépendance de Transport.
|
||||||
|
|
||||||
|
## Validation globale avant spawn
|
||||||
|
|
||||||
|
Avant activation, `RawTransactionIngestRuntimeResources::validate_network` impose :
|
||||||
|
|
||||||
|
```text
|
||||||
|
collection non vide
|
||||||
|
source_count <= 32
|
||||||
|
chaque source sur le réseau Worker
|
||||||
|
source_key unique dans la collection
|
||||||
|
```
|
||||||
|
|
||||||
|
Les erreurs restent mappées sur le contrat stable `ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID` avec conditions internes stables, sans valeurs sensibles.
|
||||||
|
|
||||||
|
## Gate volontaire d'activation multi-source
|
||||||
|
|
||||||
|
Le plan final exige que toutes les sources configurées soient démarrées simultanément. Cette responsabilité est explicitement réservée à `pre.007`.
|
||||||
|
|
||||||
|
Pour éviter un comportement faux en `pre.002` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
1 source Yellowstone : voie productive stable conservée
|
||||||
|
2..32 sources : composition/validation possible
|
||||||
|
start avec 2..32 sources : fail closed avant spawn
|
||||||
|
jamais de démarrage silencieux de la première source seulement
|
||||||
|
```
|
||||||
|
|
||||||
|
Condition interne :
|
||||||
|
|
||||||
|
```text
|
||||||
|
runtime_resources.multi_source_activation_pending
|
||||||
|
```
|
||||||
|
|
||||||
|
Ce gate sera supprimé uniquement par la tranche supervisor/source inventory.
|
||||||
|
|
||||||
|
## Preuves déterministes ajoutées
|
||||||
|
|
||||||
|
Transport :
|
||||||
|
|
||||||
|
```text
|
||||||
|
identité de request stable malgré ordre d'insertion
|
||||||
|
identité différente si le filtre logique change
|
||||||
|
Debug identité redacted
|
||||||
|
surface identité disponible depuis crate root
|
||||||
|
aucun bytes()/as_bytes() public
|
||||||
|
```
|
||||||
|
|
||||||
|
Worker :
|
||||||
|
|
||||||
|
```text
|
||||||
|
source_key stable pour identité logique identique
|
||||||
|
source_key sensible au request fingerprint
|
||||||
|
source_key sensible à l'endpoint identity sûre
|
||||||
|
32 sources exactement acceptées
|
||||||
|
33e rejetée sans mutation
|
||||||
|
duplicate source identity rejetée
|
||||||
|
source cross-network rejetée
|
||||||
|
Debug aggregate limité à source_count
|
||||||
|
activation multi-source fail-closed avant pre.007
|
||||||
|
public API canary pour bound/count/push
|
||||||
|
release export inventory mis à jour
|
||||||
|
```
|
||||||
|
|
||||||
|
## Compatibilité et non-régression
|
||||||
|
|
||||||
|
La construction historique :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawTransactionIngestRuntimeResources::new(yellowstone_source)
|
||||||
|
```
|
||||||
|
|
||||||
|
reste valide et crée un aggregate d'une source.
|
||||||
|
|
||||||
|
La voie productive stable `start_with_runtime_resources` continue donc à fonctionner pour une seule source Yellowstone sans changement de Common RAW, Store, Config ni protocole réseau.
|
||||||
|
|
||||||
|
## Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.3.13/pre.002.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-onchain-transport-lib/src/grpc_subscribe.rs
|
||||||
|
crates/ksp-onchain-transport-lib/src/lib.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_subscribe.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/tests/public_api.rs
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
|
||||||
|
docs/plans/034-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE_PLAN.md
|
||||||
|
docs/validation/030-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers supprimés
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun
|
||||||
|
```
|
||||||
|
|
||||||
|
## Versions de fichiers incrémentées
|
||||||
|
|
||||||
|
```text
|
||||||
|
grpc_subscribe.rs transport src : 8 -> 9
|
||||||
|
transport lib.rs : 46 -> 47
|
||||||
|
transport public_api.rs : 51 -> 52
|
||||||
|
transport release_completeness.rs : 43 -> 44
|
||||||
|
transport unit grpc_subscribe.rs : 5 -> 6
|
||||||
|
worker lib.rs : 17 -> 18
|
||||||
|
worker runtime.rs : 11 -> 12
|
||||||
|
worker runtime_resources.rs : 12 -> 13
|
||||||
|
worker public_api.rs : 11 -> 12
|
||||||
|
worker release_completeness.rs : 9 -> 10
|
||||||
|
worker unit runtime_resources.rs : 11 -> 12
|
||||||
|
plan 034 : 1 -> 2
|
||||||
|
validation 030 : 1 -> 2
|
||||||
|
```
|
||||||
|
|
||||||
|
## Audit intermédiaire et corrections
|
||||||
|
|
||||||
|
La première passe statique après introduction de l'identité Transport a détecté une rustdoc devenue non adjacente au `YellowstoneSubscribeRequest`. La structure documentaire a été corrigée avant poursuite.
|
||||||
|
|
||||||
|
Une passe ultérieure après ajout des canaris externes a détecté uniquement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
2 fins de fichier avec newline supplémentaire
|
||||||
|
1 double ligne vide entre items Rust
|
||||||
|
```
|
||||||
|
|
||||||
|
Ces trois écarts de format ont été corrigés avant la validation finale.
|
||||||
|
|
||||||
|
Aucun état intermédiaire en échec n'est présenté comme PASS.
|
||||||
|
|
||||||
|
## Validations exécutées localement après correction finale
|
||||||
|
|
||||||
|
```text
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py
|
||||||
|
PASS : General Rust rule audit clean
|
||||||
|
PASS : Rust export completeness audit 0 candidate
|
||||||
|
PASS : KSP workspace Rust rule audit clean
|
||||||
|
|
||||||
|
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas
|
||||||
|
PASS : Markdown table audit clean (340 tables, 829 files)
|
||||||
|
|
||||||
|
supplemental normative definition-id scan
|
||||||
|
PASS : 489 definitions, 489 unique, 0 duplicate definition id
|
||||||
|
|
||||||
|
pre-packaging exhaustive diff scan
|
||||||
|
PASS : 14 fichiers modifiés, 1 ajouté, 0 supprimé
|
||||||
|
PASS : chaque header de version des 13 fichiers versionnés modifiés est incrémenté exactement de +1
|
||||||
|
PASS : aucune nouvelle occurrence de unsafe/unwrap/expect/panic/todo/unimplemented dans les lignes Rust production ajoutées
|
||||||
|
```
|
||||||
|
|
||||||
|
Les caches Python recréés par les scripts d'audit sont supprimés après la dernière passe et ne font pas partie de la livraison. L'archive delta est ensuite contrôlée par inventaire exact, test ZIP et scan des chemins/artefacts.
|
||||||
|
|
||||||
|
## Validations non exécutées localement
|
||||||
|
|
||||||
|
Le toolchain Rust n'est pas installé dans l'environnement d'assemblage courant :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all -- --check
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||||
|
cargo test -p ksp-onchain-transport-lib
|
||||||
|
cargo test -p ksp-worker-raw-transaction-ingest-lib
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
Statut : `NON EXÉCUTÉ LOCAL`, jamais PASS.
|
||||||
|
|
||||||
|
## Décisions prises
|
||||||
|
|
||||||
|
```text
|
||||||
|
conserver 1..32 comme borne runtime P0
|
||||||
|
source identity opaque et non publique côté Worker
|
||||||
|
faire posséder l'identité exacte SubscribeRequest par Transport
|
||||||
|
ne pas ajouter prost comme dépendance Worker/Transport supplémentaire
|
||||||
|
valider toutes les sources avant spawn
|
||||||
|
rejeter duplicate identity et cross-network avant mutation
|
||||||
|
fail closed pour activation >1 jusqu'à pre.007
|
||||||
|
ne pas démarrer un sous-ensemble silencieux
|
||||||
|
ne modifier ni Common RAW ni Store API
|
||||||
|
```
|
||||||
|
|
||||||
|
## Questions ouvertes
|
||||||
|
|
||||||
|
Aucune nouvelle question bloquante pour `pre.003`.
|
||||||
|
|
||||||
|
Restent volontairement dans leurs tranches prévues :
|
||||||
|
|
||||||
|
```text
|
||||||
|
logsSubscribe standard + hydration : pre.003
|
||||||
|
blockSubscribe standard : pre.004
|
||||||
|
Helius transactionSubscribe : pre.005
|
||||||
|
HTTP polling : pre.006
|
||||||
|
supervisor simultané / source inventory : pre.007
|
||||||
|
convergence globale / observations multiples : pre.008
|
||||||
|
```
|
||||||
210
deltas/0.3.13/pre.003-fix.001.md
Normal file
210
deltas/0.3.13/pre.003-fix.001.md
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
<!-- file: deltas/0.3.13/pre.003-fix.001.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta 0.3.13-pre.003-fix.001 — correction des tests Worker après généralisation source-neutral
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
delivery précédente : 0.3.13-pre.003
|
||||||
|
Cargo base : 0.3.13-pre.3
|
||||||
|
delta base : deltas/0.3.13/pre.003.md
|
||||||
|
archive delta base : ksp-general-0.3.13-pre.003.zip
|
||||||
|
SHA-256 delta base : 669b875410799b8a2c5806cddad81740bdd4dd78e36c68f6b268a6b0257f18f4
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur de `pre.003` confirme que le formatage, les audits et `cargo check --workspace` sont propres, mais que les cibles de tests Worker ne compilent plus après la généralisation source-neutral de `runtime_resources`. `cargo clippy --workspace --all-targets --all-features -- -D warnings` et `cargo test -p ksp-worker-raw-transaction-ingest-lib` échouent sur sept diagnostics provenant du même fichier de tests.
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Corriger uniquement les tests devenus obsolètes par rapport au contrat production de `pre.003`, sans rouvrir l'API ni modifier la logique runtime :
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun code production modifié
|
||||||
|
aucune API publique ajoutée ou supprimée
|
||||||
|
aucune dépendance modifiée
|
||||||
|
aucune logique réseau/hydration/admission modifiée
|
||||||
|
aucun avancement vers blockSubscribe pre.004
|
||||||
|
```
|
||||||
|
|
||||||
|
## Version
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison : 0.3.13-pre.003-fix.001
|
||||||
|
workspace.package.version : 0.3.13-pre.3.fix.1
|
||||||
|
archive : ksp-general-0.3.13-pre.003-fix.001.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Conformément aux règles de versionnement KSP, le fix modifie un fichier `.rs`; le `workspace.package.version` est donc synchronisé avec l'identifiant SemVer technique du correctif.
|
||||||
|
|
||||||
|
## Diagnostic opérateur
|
||||||
|
|
||||||
|
Les sept diagnostics se répartissent en trois causes.
|
||||||
|
|
||||||
|
### Ancien extracteur mono-Yellowstone supprimé
|
||||||
|
|
||||||
|
Le test `v0_3_13_pre_002_multi_source_activation_fails_closed_until_supervisor_tranche` appelait encore :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawTransactionIngestRuntimeResources::into_yellowstone_source
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette méthode a été supprimée en `pre.003` lorsque l'aggregate a été rendu source-neutral. La barrière correspondante est désormais :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawTransactionIngestRuntimeResources::validate_single_source_activation
|
||||||
|
```
|
||||||
|
|
||||||
|
Le test utilise donc le contrat actuel et continue de vérifier exactement l'erreur :
|
||||||
|
|
||||||
|
```text
|
||||||
|
runtime_resources.multi_source_activation_pending
|
||||||
|
```
|
||||||
|
|
||||||
|
Les deux erreurs d'inférence `E0282` étaient secondaires à l'absence de l'ancienne méthode et disparaissent avec cette correction.
|
||||||
|
|
||||||
|
### Contexte d'hydration manquant
|
||||||
|
|
||||||
|
Deux tests appelaient encore l'ancienne signature de :
|
||||||
|
|
||||||
|
```text
|
||||||
|
build_hydration_provenance
|
||||||
|
```
|
||||||
|
|
||||||
|
`pre.003` a ajouté en premier argument le contexte source-neutral :
|
||||||
|
|
||||||
|
```text
|
||||||
|
&RawTransactionIngestHydrationContext
|
||||||
|
```
|
||||||
|
|
||||||
|
Les tests construisent maintenant ce contexte via :
|
||||||
|
|
||||||
|
```text
|
||||||
|
source.hydration_context()
|
||||||
|
```
|
||||||
|
|
||||||
|
puis le transmettent à `build_hydration_provenance`. Aucun contrat production n'est élargi pour les tests.
|
||||||
|
|
||||||
|
### Accesseurs volontairement absents de RawTransactionMaterial
|
||||||
|
|
||||||
|
Le nouveau test Standard Logs tentait d'appeler :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ingress.material.slot()
|
||||||
|
ingress.material.signature()
|
||||||
|
```
|
||||||
|
|
||||||
|
`RawTransactionMaterial` garde ces champs privés et ne fournit volontairement pas ces accesseurs. Le test ne doit pas provoquer l'ajout d'une API uniquement pour introspection.
|
||||||
|
|
||||||
|
Les deux assertions sont donc supprimées à ce niveau. La même preuve reste effectuée après passage dans l'admission centrale et canonicalisation, via :
|
||||||
|
|
||||||
|
```text
|
||||||
|
acquisition.transaction().slot() == 42
|
||||||
|
acquisition.transaction().reference().signature().as_bytes() == [0; 64]
|
||||||
|
```
|
||||||
|
|
||||||
|
Cela vérifie en plus le chemin réellement consommé par le Worker.
|
||||||
|
|
||||||
|
## Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.3.13/pre.003-fix.001.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers supprimés
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun
|
||||||
|
```
|
||||||
|
|
||||||
|
## Versions de fichiers incrémentées
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml : 542 -> 543
|
||||||
|
worker unit_tests/runtime_resources.rs : 13 -> 14
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation opérateur de la base pre.003
|
||||||
|
|
||||||
|
Le log fourni confirme avant fix :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all : exécuté
|
||||||
|
cargo fmt --all -- --check : PASS / silencieux
|
||||||
|
General Rust rule audit : clean
|
||||||
|
Rust export completeness audit : 0 candidate(s)
|
||||||
|
KSP workspace Rust rule audit : clean
|
||||||
|
Markdown table audit : clean (340 tables, 831 files)
|
||||||
|
cargo check --workspace : PASS
|
||||||
|
cargo clippy --workspace --all-targets --all-features -- -D warnings : FAIL sur compilation des tests Worker
|
||||||
|
cargo test -p ksp-worker-raw-transaction-ingest-lib : FAIL sur les mêmes 7 diagnostics
|
||||||
|
```
|
||||||
|
|
||||||
|
Diagnostics observés :
|
||||||
|
|
||||||
|
```text
|
||||||
|
E0599 : into_yellowstone_source supprimé
|
||||||
|
E0282 : 2 erreurs d'inférence secondaires
|
||||||
|
E0061 : 2 appels build_hydration_provenance sans hydration context
|
||||||
|
E0599 : RawTransactionMaterial::slot absent
|
||||||
|
E0599 : RawTransactionMaterial::signature absent
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validations dans l'environnement d'assemblage
|
||||||
|
|
||||||
|
Le toolchain Rust n'est pas disponible dans l'environnement d'assemblage. Les validations statiques KSP exécutables localement et le contrôle exhaustif du delta sont réalisés avant packaging.
|
||||||
|
|
||||||
|
Les commandes Cargo ne sont pas déclarées PASS localement :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
## Résultats statiques finaux de l'assemblage
|
||||||
|
|
||||||
|
```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, 832 files)
|
||||||
|
Normative rule definitions: 489
|
||||||
|
Unique normative IDs: 489
|
||||||
|
Duplicates: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Gate opérateur avant pre.004
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
`pre.004` ne doit démarrer qu'après retour vert de ce gate.
|
||||||
|
|
||||||
|
## Décisions
|
||||||
|
|
||||||
|
- Le fix conserve l'encapsulation de `RawTransactionMaterial`; aucun getter n'est ajouté pour satisfaire un test.
|
||||||
|
- Le fix conserve l'aggregate source-neutral introduit en `pre.003`; l'ancien extracteur mono-Yellowstone n'est pas restauré.
|
||||||
|
- Le fix conserve le contexte d'hydration source-neutral et adapte les tests à sa signature actuelle.
|
||||||
|
- Aucun changement fonctionnel de `pre.003` n'est introduit.
|
||||||
|
|
||||||
|
## Questions ouvertes
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucune
|
||||||
|
```
|
||||||
194
deltas/0.3.13/pre.003-fix.002.md
Normal file
194
deltas/0.3.13/pre.003-fix.002.md
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
<!-- file: deltas/0.3.13/pre.003-fix.002.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta 0.3.13-pre.003-fix.002 — correction des canaris de dépendance après généralisation source-neutral
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
delivery précédente : 0.3.13-pre.003-fix.001
|
||||||
|
Cargo base : 0.3.13-pre.3.fix.1
|
||||||
|
delta base : deltas/0.3.13/pre.003-fix.001.md
|
||||||
|
archive delta base : ksp-general-0.3.13-pre.003-fix.001.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur de `pre.003-fix.001` confirme que le formatage, les audits, `cargo check --workspace`, Clippy strict et les tests unitaires Worker passent. Deux canaris historiques de `tests/dependency_boundary.rs` échouent cependant parce qu'ils vérifient encore des noms et une borne d'implémentation antérieurs à la généralisation source-neutral de `pre.003`.
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Corriger uniquement ces deux canaris afin qu'ils vérifient le contrat production actuel, sans modifier le code runtime :
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun code production modifié
|
||||||
|
aucune API publique ajoutée ou supprimée
|
||||||
|
aucune dépendance modifiée
|
||||||
|
aucune logique réseau/hydration/admission modifiée
|
||||||
|
aucun avancement vers blockSubscribe pre.004
|
||||||
|
```
|
||||||
|
|
||||||
|
## Version
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison : 0.3.13-pre.003-fix.002
|
||||||
|
workspace.package.version : 0.3.13-pre.3.fix.2
|
||||||
|
archive : ksp-general-0.3.13-pre.003-fix.002.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Le fix modifie un fichier Rust de tests ; le `workspace.package.version` est donc incrémenté conformément aux règles KSP.
|
||||||
|
|
||||||
|
## Diagnostic opérateur
|
||||||
|
|
||||||
|
### Canary hydration historique
|
||||||
|
|
||||||
|
Le test :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0_3_12_pre_004_hydration_contract_uses_only_transport_facade_common_raw_and_existing_ingress
|
||||||
|
```
|
||||||
|
|
||||||
|
attendait encore les helpers mono-Yellowstone :
|
||||||
|
|
||||||
|
```text
|
||||||
|
fn finalize_yellowstone_hydration
|
||||||
|
fn fetch_yellowstone_hydration
|
||||||
|
```
|
||||||
|
|
||||||
|
`pre.003` a volontairement rendu l'hydration commune à plusieurs familles de sources et les helpers production actuels sont :
|
||||||
|
|
||||||
|
```text
|
||||||
|
fn finalize_hydration
|
||||||
|
async fn fetch_hydration
|
||||||
|
```
|
||||||
|
|
||||||
|
Le canary est mis à jour pour vérifier ces helpers source-neutral sans réintroduire les anciens noms.
|
||||||
|
|
||||||
|
### Canary de borne de coalescence historique
|
||||||
|
|
||||||
|
Le test :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0_3_12_pre_006_productive_source_uses_transport_session_bounded_coalescence_and_existing_admission
|
||||||
|
```
|
||||||
|
|
||||||
|
attendait encore la présence textuelle de :
|
||||||
|
|
||||||
|
```text
|
||||||
|
MAX_RAW_TRANSACTION_INGEST_ADMISSION_QUEUE_CAPACITY
|
||||||
|
```
|
||||||
|
|
||||||
|
`pre.003` a resserré le budget du coordinator commun sur la borne configurée du Worker :
|
||||||
|
|
||||||
|
```text
|
||||||
|
max_pending_signals: settings.admission_queue_capacity()
|
||||||
|
```
|
||||||
|
|
||||||
|
Le canary vérifie désormais cette borne runtime effective. Le plafond absolu n'est pas réintroduit dans l'implémentation.
|
||||||
|
|
||||||
|
## Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.3.13/pre.003-fix.002.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers supprimés
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun
|
||||||
|
```
|
||||||
|
|
||||||
|
## Versions de fichiers incrémentées
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml : 543 -> 544
|
||||||
|
worker tests/dependency_boundary.rs : 17 -> 18
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation opérateur de la base pre.003-fix.001
|
||||||
|
|
||||||
|
Le log fourni confirme avant ce fix :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all : exécuté
|
||||||
|
cargo fmt --all -- --check : PASS / silencieux
|
||||||
|
General Rust rule audit : clean
|
||||||
|
Rust export completeness audit : 0 candidate(s)
|
||||||
|
KSP workspace Rust rule audit : clean
|
||||||
|
Markdown table audit : clean (340 tables, 832 files)
|
||||||
|
cargo check --workspace : PASS
|
||||||
|
cargo clippy --workspace --all-targets --all-features -- -D warnings : FAIL lors des tests dependency_boundary
|
||||||
|
cargo test -p ksp-worker-raw-transaction-ingest-lib : 75 unit tests PASS, puis 2/10 dependency_boundary FAIL
|
||||||
|
```
|
||||||
|
|
||||||
|
Échecs observés :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0_3_12_pre_004_hydration_contract_uses_only_transport_facade_common_raw_and_existing_ingress
|
||||||
|
v0_3_12_pre_006_productive_source_uses_transport_session_bounded_coalescence_and_existing_admission
|
||||||
|
```
|
||||||
|
|
||||||
|
Les deux échecs sont des assertions `include_str!` sur l'implémentation et non des erreurs du code production.
|
||||||
|
|
||||||
|
## Validations dans l'environnement d'assemblage
|
||||||
|
|
||||||
|
Le toolchain Rust n'est pas disponible dans l'environnement d'assemblage. Les audits statiques KSP et le contrôle exhaustif du delta sont exécutés avant packaging. Les commandes Cargo suivantes ne sont donc pas déclarées PASS localement :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
## Résultats statiques finaux de l'assemblage
|
||||||
|
|
||||||
|
```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, 833 files)
|
||||||
|
Normative rule definitions: 489
|
||||||
|
Unique normative IDs: 489
|
||||||
|
Duplicates: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
Les trois chaînes attendues par les canaris corrigés ont également été vérifiées directement dans `runtime_resources.rs` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
fn finalize_hydration : présent
|
||||||
|
fn fetch_hydration : présent
|
||||||
|
max_pending_signals: settings.admission_queue_capacity() : présent
|
||||||
|
```
|
||||||
|
|
||||||
|
## Gate opérateur avant pre.004
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
`pre.004` ne doit démarrer qu'après retour vert de ce gate.
|
||||||
|
|
||||||
|
## Décisions
|
||||||
|
|
||||||
|
- Le fix conserve les helpers d'hydration source-neutral de `pre.003`; les anciens noms Yellowstone ne sont pas restaurés.
|
||||||
|
- Le fix conserve `settings.admission_queue_capacity()` comme borne effective du coordinator d'hydration ; le plafond absolu n'est pas réinjecté.
|
||||||
|
- Aucun code production n'est modifié.
|
||||||
|
- Aucun changement fonctionnel de `pre.003` n'est introduit.
|
||||||
|
|
||||||
|
## Questions ouvertes
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucune
|
||||||
|
```
|
||||||
164
deltas/0.3.13/pre.003-fix.003.md
Normal file
164
deltas/0.3.13/pre.003-fix.003.md
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
<!-- file: deltas/0.3.13/pre.003-fix.003.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta 0.3.13-pre.003-fix.003 — correction du dernier canari productif historique
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
delivery précédente : 0.3.13-pre.003-fix.002
|
||||||
|
Cargo base : 0.3.13-pre.3.fix.2
|
||||||
|
delta base : deltas/0.3.13/pre.003-fix.002.md
|
||||||
|
archive delta base : ksp-general-0.3.13-pre.003-fix.002.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur de `pre.003-fix.002` confirme que le formatage, les audits, `cargo check --workspace`, les 75 tests unitaires Worker et 9/10 canaris `dependency_boundary` passent. Un seul canari historique reste obsolète : il recherche encore la chaîne `commitment: commitment.as_str()` alors que `pre.003` a déplacé le commitment dans le contexte d'hydration source-neutral.
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Corriger uniquement cette assertion textuelle de test afin qu'elle vérifie le contrat production actuel :
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun code production modifié
|
||||||
|
aucune API publique ajoutée ou supprimée
|
||||||
|
aucune dépendance modifiée
|
||||||
|
aucune logique réseau/hydration/admission modifiée
|
||||||
|
aucun avancement vers blockSubscribe pre.004
|
||||||
|
```
|
||||||
|
|
||||||
|
## Version
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison : 0.3.13-pre.003-fix.003
|
||||||
|
workspace.package.version : 0.3.13-pre.3.fix.3
|
||||||
|
archive : ksp-general-0.3.13-pre.003-fix.003.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Le fix modifie un fichier Rust de tests ; le `workspace.package.version` est donc incrémenté conformément aux règles KSP.
|
||||||
|
|
||||||
|
## Diagnostic opérateur
|
||||||
|
|
||||||
|
Le test :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0_3_12_pre_006_productive_source_uses_transport_session_bounded_coalescence_and_existing_admission
|
||||||
|
```
|
||||||
|
|
||||||
|
attendait encore :
|
||||||
|
|
||||||
|
```text
|
||||||
|
commitment: commitment.as_str()
|
||||||
|
```
|
||||||
|
|
||||||
|
Le coordinator d'hydration source-neutral de `pre.003` porte désormais le commitment dans `RawTransactionIngestHydrationContext`. Le code production courant construit donc la clé avec :
|
||||||
|
|
||||||
|
```text
|
||||||
|
commitment: hydration.commitment.as_str()
|
||||||
|
```
|
||||||
|
|
||||||
|
Le canari est mis à jour pour vérifier cette forme actuelle. Aucun alias, variable ou code de compatibilité artificiel n'est ajouté au runtime.
|
||||||
|
|
||||||
|
## Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.3.13/pre.003-fix.003.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers supprimés
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun
|
||||||
|
```
|
||||||
|
|
||||||
|
## Versions de fichiers incrémentées
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml : 544 -> 545
|
||||||
|
worker tests/dependency_boundary.rs : 18 -> 19
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation opérateur de la base pre.003-fix.002
|
||||||
|
|
||||||
|
Le log fourni confirme avant ce fix :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all : exécuté
|
||||||
|
cargo fmt --all -- --check : PASS / silencieux
|
||||||
|
General Rust rule audit : clean
|
||||||
|
Rust export completeness audit : 0 candidate(s)
|
||||||
|
KSP workspace Rust rule audit : clean
|
||||||
|
Markdown table audit : clean (340 tables, 833 files)
|
||||||
|
cargo check --workspace : PASS
|
||||||
|
cargo clippy --workspace --all-targets --all-features -- -D warnings : atteint les tests puis échoue sur le même canari dependency_boundary
|
||||||
|
cargo test -p ksp-worker-raw-transaction-ingest-lib : 75 unit tests PASS, puis 9/10 dependency_boundary PASS et 1 FAIL
|
||||||
|
```
|
||||||
|
|
||||||
|
Échec restant :
|
||||||
|
|
||||||
|
```text
|
||||||
|
required pre.006 productive-source contract missing: commitment: commitment.as_str()
|
||||||
|
```
|
||||||
|
|
||||||
|
Il s'agit d'une assertion `include_str!` obsolète sur l'implémentation, pas d'une erreur du code production.
|
||||||
|
|
||||||
|
## Validations dans l'environnement d'assemblage
|
||||||
|
|
||||||
|
Le toolchain Rust n'est pas disponible dans l'environnement d'assemblage. Les audits statiques KSP et le contrôle exhaustif du delta sont exécutés avant packaging. Les commandes Cargo suivantes ne sont donc pas déclarées PASS localement :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
## Résultats statiques finaux de l'assemblage
|
||||||
|
|
||||||
|
```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, 834 files)
|
||||||
|
Normative rule definitions: 489
|
||||||
|
Unique normative IDs: 489
|
||||||
|
Duplicates: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
La chaîne attendue par le canari corrigé a également été vérifiée directement dans `runtime_resources.rs` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
commitment: hydration.commitment.as_str() : présent
|
||||||
|
```
|
||||||
|
|
||||||
|
## Gate opérateur avant pre.004
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
`pre.004` ne doit démarrer qu'après retour vert de ce gate.
|
||||||
|
|
||||||
|
## Décisions
|
||||||
|
|
||||||
|
- Le commitment reste porté par le contexte d'hydration source-neutral de `pre.003`.
|
||||||
|
- Le canari historique est aligné sur le contrat actuel ; aucun code production n'est modifié pour satisfaire une recherche textuelle obsolète.
|
||||||
|
- Aucun changement fonctionnel de `pre.003` n'est introduit.
|
||||||
|
|
||||||
|
## Questions ouvertes
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucune
|
||||||
|
```
|
||||||
260
deltas/0.3.13/pre.003.md
Normal file
260
deltas/0.3.13/pre.003.md
Normal file
@@ -0,0 +1,260 @@
|
|||||||
|
<!-- file: deltas/0.3.13/pre.003.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta 0.3.13-pre.003 — Standard Solana logsSubscribe + hydration commune
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
delivery précédente : 0.3.13-pre.002-fix.001
|
||||||
|
Cargo base : 0.3.13-pre.2.fix.1
|
||||||
|
delta base : deltas/0.3.13/pre.002-fix.001.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur de la base a été exécuté après `cargo clean` et est vert : `cargo fmt --all -- --check` silencieux, audits Rust/Markdown propres, `cargo check --workspace`, Clippy strict, 389 tests unitaires Transport, 52 tests public API Transport, 44 tests release Transport, 4 doc-tests Transport, puis 70 tests unitaires Worker et toutes ses suites d'intégration sans échec. Les smokes réseau opt-in sont restés ignorés comme prévu.
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Ajouter la première nouvelle famille live de `0.3.13` sans dupliquer Transport ni l'admission RAW :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Solana standard WS logsSubscribe
|
||||||
|
-> context.slot + signature
|
||||||
|
-> hydration HTTP getTransaction observed
|
||||||
|
-> Common RAW
|
||||||
|
-> admission centrale Worker
|
||||||
|
-> Store
|
||||||
|
```
|
||||||
|
|
||||||
|
La tranche ne démarre toujours qu'une seule source productive à la fois. La supervision simultanée de plusieurs sources reste réservée à `pre.007`.
|
||||||
|
|
||||||
|
## Version
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison : 0.3.13-pre.003
|
||||||
|
workspace.package.version : 0.3.13-pre.3
|
||||||
|
archive attendue : ksp-general-0.3.13-pre.003.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
## Source Standard Logs
|
||||||
|
|
||||||
|
Nouvelle surface publique Worker :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawTransactionIngestStandardLogsSource
|
||||||
|
```
|
||||||
|
|
||||||
|
Construction caller-composed :
|
||||||
|
|
||||||
|
```text
|
||||||
|
WsEndpointSettings kind solana_standard
|
||||||
|
SolanaLogsSubscribeFilter
|
||||||
|
SolanaCommitment Confirmed ou Finalized
|
||||||
|
HttpTransportPool
|
||||||
|
HttpRoleName hydration
|
||||||
|
```
|
||||||
|
|
||||||
|
La construction ne réalise aucune I/O. Elle valide le protocole WebSocket, le commitment, le réseau, la représentation sûre provider/endpoint et l'existence d'une route HTTP `getTransaction` compatible sur le même cluster.
|
||||||
|
|
||||||
|
Au runtime, la source utilise exclusivement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
SolanaStandardWsSession::connect
|
||||||
|
logs_subscribe
|
||||||
|
HttpTransportPool::get_transaction_observed
|
||||||
|
```
|
||||||
|
|
||||||
|
Le Worker ne construit aucun socket/client inférieur et ne dépend directement ni de `reqwest`, ni de `tokio-tungstenite`, ni de `tonic`, ni de `yellowstone-grpc-proto`.
|
||||||
|
|
||||||
|
## Projection et redaction
|
||||||
|
|
||||||
|
Une notification `logsSubscribe` est réduite à son matériau de référence nécessaire :
|
||||||
|
|
||||||
|
```text
|
||||||
|
context.slot
|
||||||
|
signature
|
||||||
|
commitment caller-composed
|
||||||
|
identité source/filter privée
|
||||||
|
```
|
||||||
|
|
||||||
|
Les champs distants `logs` et `err` ne sont pas copiés dans le signal Worker, le snapshot, la provenance textuelle ou `Debug`.
|
||||||
|
|
||||||
|
Les variantes :
|
||||||
|
|
||||||
|
```text
|
||||||
|
All
|
||||||
|
AllWithVotes
|
||||||
|
Mentions(pubkey)
|
||||||
|
```
|
||||||
|
|
||||||
|
participent à une empreinte SHA-256 privée. La valeur du pubkey `Mentions` n'est pas exposée par le Worker.
|
||||||
|
|
||||||
|
## Identité logique
|
||||||
|
|
||||||
|
Le `source_key` Standard Logs est dérivé de :
|
||||||
|
|
||||||
|
```text
|
||||||
|
domain KSP live-source v1
|
||||||
|
family = standard_logs
|
||||||
|
network
|
||||||
|
safe provider identity
|
||||||
|
safe endpoint identity
|
||||||
|
commitment
|
||||||
|
private filter fingerprint
|
||||||
|
```
|
||||||
|
|
||||||
|
Le rôle et le pool HTTP d'hydration n'entrent pas dans cette identité : ils décrivent la stratégie d'enrichissement, pas une nouvelle source live.
|
||||||
|
|
||||||
|
Le rejet transactionnel des doublons et le bound global `1..32` introduits en `pre.002` s'appliquent aussi à Standard Logs.
|
||||||
|
|
||||||
|
## Hydration source-neutral
|
||||||
|
|
||||||
|
Le coordinator privé précédemment utilisé par Yellowstone est généralisé en contrat d'hydration source-neutral et réutilisé par Standard Logs.
|
||||||
|
|
||||||
|
Pour `pre.003`, chaque exécution mono-source possède encore sa propre instance. Le partage réellement global entre plusieurs tâches source sera introduit avec le supervisor `pre.007`.
|
||||||
|
|
||||||
|
Bornes effectives :
|
||||||
|
|
||||||
|
```text
|
||||||
|
hydration in-flight <= persistence_concurrency
|
||||||
|
pending source signals <= admission_queue_capacity
|
||||||
|
```
|
||||||
|
|
||||||
|
Le plafond pending est donc aligné sur les settings effectifs du Worker plutôt que sur le plafond absolu maximal.
|
||||||
|
|
||||||
|
La requête commune `getTransaction observed` utilise maintenant :
|
||||||
|
|
||||||
|
```text
|
||||||
|
encoding = base64
|
||||||
|
maxSupportedTransactionVersion = 1
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette adaptation s'applique également à Yellowstone. Elle prépare l'hydration aux transactions Legacy/V0/V1 mais ne qualifie aucun nouveau chemin RAW-direct V1.
|
||||||
|
|
||||||
|
## Activation runtime
|
||||||
|
|
||||||
|
Le runtime-resource aggregate peut composer Yellowstone et Standard Logs dans la collection privée bornée, mais la barrière de sûreté reste :
|
||||||
|
|
||||||
|
```text
|
||||||
|
1 source Yellowstone : productif
|
||||||
|
1 source Standard Logs : productif
|
||||||
|
2..32 sources : composition validable
|
||||||
|
start 2..32 : runtime_resources.multi_source_activation_pending avant spawn
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune source configurée n'est silencieusement ignorée.
|
||||||
|
|
||||||
|
## Preuves ajoutées
|
||||||
|
|
||||||
|
Unit tests Worker :
|
||||||
|
|
||||||
|
```text
|
||||||
|
validation standard WS / commitment / route HTTP
|
||||||
|
source_key stable et filter-sensitive
|
||||||
|
source_key indépendant du hydration role
|
||||||
|
All / AllWithVotes / Mentions distingués
|
||||||
|
Mentions/URL/source-key bytes redacted
|
||||||
|
projection reference-only
|
||||||
|
fixture signature+slot -> une hydration HTTP -> matériau Common RAW exact
|
||||||
|
getTransaction base64 + maxSupportedTransactionVersion=1
|
||||||
|
rejet duplicate Standard Logs identity
|
||||||
|
```
|
||||||
|
|
||||||
|
Canaris externes :
|
||||||
|
|
||||||
|
```text
|
||||||
|
public_api : nouvelle construction/composition accessible depuis crate root
|
||||||
|
dependency_boundary : uniquement façades Transport + Common RAW/Store existants
|
||||||
|
hardening : aucune copie logs/err/payload/url et Debug borné
|
||||||
|
release_completeness : nouvelle surface et canaris pre.003 obligatoires
|
||||||
|
```
|
||||||
|
|
||||||
|
Reconnect, resubscribe, IDs distants et backpressure WebSocket restent couverts et possédés par `ksp-onchain-transport-lib`; le Worker réutilise la façade `SolanaStandardWsSession` au lieu de reproduire ces mécanismes.
|
||||||
|
|
||||||
|
## Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/README.md
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/USAGE.md
|
||||||
|
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/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
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
|
||||||
|
docs/plans/034-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE_PLAN.md
|
||||||
|
docs/validation/030-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.3.13/pre.003.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers supprimés
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun
|
||||||
|
```
|
||||||
|
|
||||||
|
## Non-claims
|
||||||
|
|
||||||
|
`pre.003` ne prétend pas :
|
||||||
|
|
||||||
|
```text
|
||||||
|
blockSubscribe productif
|
||||||
|
Helius transactionSubscribe productif
|
||||||
|
HTTP live block polling
|
||||||
|
supervision simultanée de plusieurs sources
|
||||||
|
fanout Store d'observations multi-source
|
||||||
|
coordinator partagé entre plusieurs tâches source simultanées
|
||||||
|
gap repair/backfill
|
||||||
|
nouvelle dépendance externe
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation dans l'environnement d'assemblage
|
||||||
|
|
||||||
|
Les contrôles réellement exécutés après finalisation de la tranche sont :
|
||||||
|
|
||||||
|
```text
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py
|
||||||
|
PASS : General Rust rule audit clean
|
||||||
|
PASS : Rust export completeness audit 0 candidate
|
||||||
|
PASS : KSP workspace Rust rule audit clean
|
||||||
|
|
||||||
|
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas
|
||||||
|
PASS : Markdown table audit clean (340 tables, 831 files)
|
||||||
|
|
||||||
|
supplemental normative definition-id scan
|
||||||
|
PASS : 489 definitions, 489 unique, 0 duplicate definition id
|
||||||
|
|
||||||
|
pre-packaging exhaustive diff scan
|
||||||
|
PASS : 13 fichiers modifiés, 1 ajouté, 0 supprimé
|
||||||
|
PASS : les 13 headers versionnés des fichiers modifiés sont incrémentés exactement de +1
|
||||||
|
PASS : aucune dépendance Cargo nouvelle
|
||||||
|
```
|
||||||
|
|
||||||
|
Le toolchain Rust/Cargo/Rustfmt n'est pas disponible dans cet environnement. Les commandes Cargo post-modification sont donc explicitement `NON EXÉCUTÉ LOCAL`, jamais déclarées PASS.
|
||||||
|
|
||||||
|
Les caches Python produits par les audits sont supprimés avant packaging et ne font pas partie du delta.
|
||||||
|
|
||||||
|
## Gate opérateur requis avant pre.004
|
||||||
|
|
||||||
|
```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
|
||||||
|
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||||
|
cargo test -p ksp-onchain-transport-lib
|
||||||
|
cargo test -p ksp-worker-raw-transaction-ingest-lib
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
`pre.004` reste bloquée si ce gate révèle une anomalie de format, règle, compilation, API, dépendance ou test.
|
||||||
94
deltas/0.3.13/pre.004-fix.001.md
Normal file
94
deltas/0.3.13/pre.004-fix.001.md
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
<!-- file: deltas/0.3.13/pre.004-fix.001.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta 0.3.13-pre.004-fix.001 — retrait de la dépendance serde_json accidentelle
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison précédente : 0.3.13-pre.004
|
||||||
|
Cargo base : 0.3.13-pre.4
|
||||||
|
delta base : deltas/0.3.13/pre.004.md
|
||||||
|
archive delta base : ksp-general-0.3.13-pre.004.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur de `pre.004` a révélé deux erreurs de compilation liées au même défaut : le Worker nommait directement `serde_json::Value` dans `runtime_resources.rs` alors que `serde_json` n'est pas une dépendance du crate, et le test de qualification Standard Block utilisait directement `serde_json::json!`.
|
||||||
|
|
||||||
|
## Objectif du fix
|
||||||
|
|
||||||
|
Supprimer cette dépendance accidentelle sans ajouter `serde_json` au manifeste du Worker et sans modifier le comportement productif de Standard Block.
|
||||||
|
|
||||||
|
Le chemin production conserve exactement le `meta` décodé par `ksp-onchain-transport-lib`, mais son type concret reste inféré depuis `SolanaBlockTransaction::meta()` et n'est plus nommé par le Worker.
|
||||||
|
|
||||||
|
Le helper de qualification reçoit désormais seulement la version, la position et une closure de construction du matériau. La closure production capture le `meta` opaque et le clone vers le Common RAW. La fixture Legacy/V0/V1 utilise un `RawTransactionWireField::Null`, car elle vérifie la signature embarquée, le slot, le `blockTime`, la version et le `transactionIndex`, pas le contenu de `meta`.
|
||||||
|
|
||||||
|
## Version
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison : 0.3.13-pre.004-fix.001
|
||||||
|
workspace.package.version : 0.3.13-pre.4.fix.1
|
||||||
|
archive : ksp-general-0.3.13-pre.004-fix.001.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
`Cargo.toml` passe de la version de fichier `546` à `547`.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
```
|
||||||
|
|
||||||
|
Les deux fichiers Rust passent de la version de fichier `15` à `16`.
|
||||||
|
|
||||||
|
## Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.3.13/pre.004-fix.001.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers supprimés
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun
|
||||||
|
```
|
||||||
|
|
||||||
|
## Non-claims
|
||||||
|
|
||||||
|
Ce fix n'ajoute aucune dépendance, ne modifie aucune API publique et ne change ni la qualification Legacy/V0/V1, ni l'admission RAW, ni la frontier, ni le comportement réseau de `blockSubscribe`.
|
||||||
|
|
||||||
|
## Validation dans l'environnement d'assemblage
|
||||||
|
|
||||||
|
Le toolchain Rust/Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Les commandes Cargo post-fix restent donc `NON EXÉCUTÉ LOCAL`. Les audits statiques KSP et le contrôle exhaustif du delta sont exécutés avant packaging.
|
||||||
|
|
||||||
|
## Gate opérateur requis avant pre.005
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
## Résultats statiques finaux de l'assemblage
|
||||||
|
|
||||||
|
```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, 836 files)
|
||||||
|
```
|
||||||
|
|
||||||
|
Contrôle exhaustif du delta :
|
||||||
|
|
||||||
|
```text
|
||||||
|
3 fichiers existants modifiés
|
||||||
|
1 fichier ajouté
|
||||||
|
0 fichier supprimé
|
||||||
|
aucune dépendance ajoutée
|
||||||
|
aucune référence directe serde_json dans les deux fichiers Rust corrigés
|
||||||
|
```
|
||||||
267
deltas/0.3.13/pre.004.md
Normal file
267
deltas/0.3.13/pre.004.md
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
<!-- file: deltas/0.3.13/pre.004.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta 0.3.13-pre.004 — Standard Solana blockSubscribe RAW-direct qualifié
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
delivery précédente : 0.3.13-pre.003-fix.003
|
||||||
|
Cargo base : 0.3.13-pre.3.fix.3
|
||||||
|
delta base : deltas/0.3.13/pre.003-fix.003.md
|
||||||
|
archive delta base : ksp-general-0.3.13-pre.003-fix.003.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur reçu sur cette base est vert pour toutes les commandes exécutées : `cargo fmt`, `cargo fmt --check`, audits Rust/Markdown, `cargo check --workspace`, Clippy strict et toutes les suites de `ksp-worker-raw-transaction-ingest-lib` (`75` unit, `4` cross-layer, `10` dependency-boundary, `17` hardening, `11` public-api, `4` release-completeness, `0` doc-test). Cette exécution ne contenait pas le test Transport ni les commandes `cargo tree`; ils ne sont donc pas déclarés PASS pour ce gate précis.
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Ajouter la deuxième source Solana Standard WebSocket de `0.3.13` sans dupliquer Transport :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Solana standard WS blockSubscribe
|
||||||
|
-> Full + Base64 + maxSupportedTransactionVersion=1 + showRewards=false
|
||||||
|
-> qualification explicite Legacy / V0 / V1
|
||||||
|
-> Common RAW direct par transaction
|
||||||
|
-> admission centrale Worker
|
||||||
|
-> Store
|
||||||
|
```
|
||||||
|
|
||||||
|
La source ne possède pas de fallback HTTP. Tout bloc ou transaction non qualifiable échoue explicitement et sûrement.
|
||||||
|
|
||||||
|
## Version
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison : 0.3.13-pre.004
|
||||||
|
workspace.package.version : 0.3.13-pre.4
|
||||||
|
archive : ksp-general-0.3.13-pre.004.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
`Cargo.toml` passe également de la version de fichier `545` à `546`.
|
||||||
|
|
||||||
|
## Source Standard Block
|
||||||
|
|
||||||
|
Nouvelle surface publique Worker :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawTransactionIngestStandardBlockSource
|
||||||
|
RawTransactionIngestRuntimeResources::from_standard_block_source
|
||||||
|
RawTransactionIngestRuntimeResources::try_push_standard_block_source
|
||||||
|
```
|
||||||
|
|
||||||
|
Construction caller-composed :
|
||||||
|
|
||||||
|
```text
|
||||||
|
WsEndpointSettings kind solana_standard
|
||||||
|
SolanaBlockSubscribeFilter
|
||||||
|
SolanaCommitment Confirmed ou Finalized
|
||||||
|
```
|
||||||
|
|
||||||
|
La construction est sans I/O. Elle valide le protocole, le commitment, le réseau et la représentation sûre provider/endpoint avant spawn.
|
||||||
|
|
||||||
|
Au runtime, la source réutilise exclusivement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
SolanaStandardWsSession::connect
|
||||||
|
SolanaStandardWsSession::block_subscribe
|
||||||
|
```
|
||||||
|
|
||||||
|
Configuration exacte :
|
||||||
|
|
||||||
|
```text
|
||||||
|
encoding = Base64
|
||||||
|
transactionDetails = Full
|
||||||
|
maxSupportedTransactionVersion = 1
|
||||||
|
showRewards = false
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun client WebSocket direct, `getBlock`, `getTransaction`, `reqwest`, `tokio-tungstenite`, `tonic` ou SDK provider n'est ajouté au Worker.
|
||||||
|
|
||||||
|
## Qualification RAW-direct
|
||||||
|
|
||||||
|
La version est fail-closed :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Legacy -> RawTransactionVersion::Legacy
|
||||||
|
0 -> RawTransactionVersion::Number(0)
|
||||||
|
1 -> RawTransactionVersion::Number(1)
|
||||||
|
>1 -> source.standard_block_transaction_version_unsupported
|
||||||
|
omise/null -> source.standard_block_transaction_version_unqualified
|
||||||
|
```
|
||||||
|
|
||||||
|
Le matériau direct conserve :
|
||||||
|
|
||||||
|
```text
|
||||||
|
signature embarquée dans le wire Base64
|
||||||
|
slot
|
||||||
|
blockTime
|
||||||
|
meta
|
||||||
|
version qualifiée
|
||||||
|
transactionIndex dans le bloc
|
||||||
|
```
|
||||||
|
|
||||||
|
Les fixtures locales construisent et sérialisent des wires Legacy, V0 et V1 avec les types publics de `ksp-raw-transaction-lib`, puis vérifient le Common RAW canonique obtenu. La preuve V1 ne repose donc pas uniquement sur une étiquette `version=1`.
|
||||||
|
|
||||||
|
La parité historique `blockSubscribe` / `getBlock` Legacy reste déjà matérialisée dans `ksp-job-backfill-lib/tests/ws_raw_parity.rs`; `pre.004` rejoue localement les matérialisations Legacy/V0 et ajoute la qualification V1.
|
||||||
|
|
||||||
|
## block:null et formes ambiguës
|
||||||
|
|
||||||
|
Les cas suivants sont des fautes sûres avant progression de frontier :
|
||||||
|
|
||||||
|
```text
|
||||||
|
context.slot différent du slot de notification
|
||||||
|
notification err non null
|
||||||
|
block null
|
||||||
|
transactions omitted/null
|
||||||
|
transaction non Base64
|
||||||
|
version omise/null/>1
|
||||||
|
signature embarquée invalide
|
||||||
|
transaction index non représentable en u32
|
||||||
|
```
|
||||||
|
|
||||||
|
`block: null` n'est jamais assimilé à un bloc vide ou à un slot traité. Le décodage Transport qui distingue `block:null` et `err` est conservé tel quel.
|
||||||
|
|
||||||
|
Pour un bloc non vide, le slot n'est enregistré settled qu'après l'admission réussie de toutes ses transactions. La voie RAW-direct n'incrémente pas `hydration_pending`; le processing frontier ne peut donc pas avancer transitoirement au milieu du traitement d'un même bloc et un stop/faute partiel ne devient jamais une progression.
|
||||||
|
|
||||||
|
## Identité logique et redaction
|
||||||
|
|
||||||
|
Le `source_key` Standard Block est dérivé de :
|
||||||
|
|
||||||
|
```text
|
||||||
|
domain KSP live-source v1
|
||||||
|
family = standard_block
|
||||||
|
network
|
||||||
|
safe provider identity
|
||||||
|
safe endpoint identity
|
||||||
|
commitment
|
||||||
|
private block-filter fingerprint
|
||||||
|
```
|
||||||
|
|
||||||
|
Les filtres `All` et `MentionsAccountOrProgram(pubkey)` sont distingués par empreinte privée. Le pubkey brut n'est pas recopié dans `Debug`, snapshot ou provenance textuelle.
|
||||||
|
|
||||||
|
Le bound global `1..32`, l'invariant réseau unique et le rejet transactionnel des doublons de `pre.002` s'appliquent à cette nouvelle famille.
|
||||||
|
|
||||||
|
## Activation runtime
|
||||||
|
|
||||||
|
Le contrat reste volontairement mono-source avant `pre.007` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
1 source Yellowstone : productif
|
||||||
|
1 source Standard Logs : productif
|
||||||
|
1 source Standard Block : productif
|
||||||
|
2..32 sources : composition validable
|
||||||
|
start 2..32 : runtime_resources.multi_source_activation_pending avant spawn
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune source configurée n'est silencieusement ignorée.
|
||||||
|
|
||||||
|
## Preuves ajoutées
|
||||||
|
|
||||||
|
Unit tests Worker :
|
||||||
|
|
||||||
|
```text
|
||||||
|
validation protocole/commitment Standard Block
|
||||||
|
source_key stable et filter-sensitive
|
||||||
|
redaction filtre/url/source-key
|
||||||
|
qualification exacte Legacy/V0/V1
|
||||||
|
rejet version omise/null/>1
|
||||||
|
wires Legacy/V0/V1 -> Common RAW direct avec signature/version/index exacts
|
||||||
|
rejet duplicate Standard Block identity
|
||||||
|
```
|
||||||
|
|
||||||
|
Canaris externes :
|
||||||
|
|
||||||
|
```text
|
||||||
|
public_api : construction/composition depuis crate root
|
||||||
|
dependency_boundary : façade Transport seulement, sans fallback HTTP/direct network crate
|
||||||
|
hardening : block:null, erreurs, version, encoding et redaction explicites
|
||||||
|
release_completeness : export et canaris pre.004 obligatoires
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/README.md
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/USAGE.md
|
||||||
|
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/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
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
|
||||||
|
docs/plans/034-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE_PLAN.md
|
||||||
|
docs/validation/030-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.3.13/pre.004.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers supprimés
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun
|
||||||
|
```
|
||||||
|
|
||||||
|
## Non-claims
|
||||||
|
|
||||||
|
`pre.004` ne prétend pas :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Helius transactionSubscribe productif
|
||||||
|
HTTP live block polling
|
||||||
|
supervision simultanée de plusieurs sources
|
||||||
|
fanout Store d'observations multi-source
|
||||||
|
fallback HTTP getBlock/getTransaction pour Standard Block
|
||||||
|
RAW-direct pour version >1 ou version ambiguë
|
||||||
|
block:null considéré comme progression
|
||||||
|
nouvelle dépendance externe
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation dans l'environnement d'assemblage
|
||||||
|
|
||||||
|
Le toolchain Rust/Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Les commandes Cargo post-`pre.004` restent donc `NON EXÉCUTÉ LOCAL`. Les audits statiques KSP, le scan des identifiants normatifs et le contrôle exhaustif du diff sont exécutés avant packaging et leurs résultats finaux sont consignés ci-dessous avant livraison.
|
||||||
|
|
||||||
|
## Gate opérateur requis avant pre.005
|
||||||
|
|
||||||
|
```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
|
||||||
|
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||||
|
cargo test -p ksp-onchain-transport-lib
|
||||||
|
cargo test -p ksp-worker-raw-transaction-ingest-lib
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
`pre.005` ne doit pas commencer si ce gate révèle une anomalie.
|
||||||
|
|
||||||
|
## Résultats statiques finaux de l'assemblage
|
||||||
|
|
||||||
|
```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, 835 files)
|
||||||
|
Normative rule definitions: 489
|
||||||
|
Unique normative IDs: 489
|
||||||
|
Duplicates: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
Contrôle exhaustif avant packaging :
|
||||||
|
|
||||||
|
```text
|
||||||
|
12 fichiers existants modifiés
|
||||||
|
1 fichier ajouté
|
||||||
|
0 fichier supprimé
|
||||||
|
12/12 headers versionnés incrémentés exactement de +1
|
||||||
|
Cargo.toml hors header : uniquement workspace.package.version 0.3.13-pre.3.fix.3 -> 0.3.13-pre.4
|
||||||
|
aucune dépendance ajoutée, supprimée ou modifiée
|
||||||
|
```
|
||||||
94
deltas/0.3.13/pre.005-fix.001.md
Normal file
94
deltas/0.3.13/pre.005-fix.001.md
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
<!-- file: deltas/0.3.13/pre.005-fix.001.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta 0.3.13-pre.005-fix.001 — correction du canari de redaction Helius
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison précédente : 0.3.13-pre.005
|
||||||
|
Cargo base : 0.3.13-pre.5
|
||||||
|
delta base : deltas/0.3.13/pre.005.md
|
||||||
|
archive delta base : ksp-general-0.3.13-pre.005.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur du 10 septembre 2026 sur `pre.005` est vert pour `cargo fmt`, `cargo fmt --check`, les audits Rust/Markdown, `cargo check --workspace`, Clippy strict, les `84` tests unitaires Worker et les `12` tests `dependency_boundary`. Il s'arrête ensuite sur un seul test de hardening : `v0_3_12_pre_002_production_sources_keep_transport_confined_to_runtime_resources` détecte le mot `credential` dans `runtime_resources.rs`.
|
||||||
|
|
||||||
|
L'occurrence concernée se trouve uniquement dans une Rustdoc décrivant l'encapsulation des paramètres Transport Helius. Aucun secret, aucune credential et aucune valeur sensible n'est stocké en clair dans le Worker par cette chaîne documentaire.
|
||||||
|
|
||||||
|
## Objectif du fix
|
||||||
|
|
||||||
|
Conserver le canari de hardening inchangé et reformuler la Rustdoc production afin qu'elle respecte sa politique textuelle stricte. La phrase emploie désormais `sensitive endpoint material` au lieu de `credentials`.
|
||||||
|
|
||||||
|
Aucune logique Helius, WebSocket, hydration, source identity, admission, Store, frontier ou API publique n'est modifiée.
|
||||||
|
|
||||||
|
## Version
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison : 0.3.13-pre.005-fix.001
|
||||||
|
workspace.package.version : 0.3.13-pre.5.fix.1
|
||||||
|
archive : ksp-general-0.3.13-pre.005-fix.001.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
`Cargo.toml` passe de la version de fichier `548` à `549`.
|
||||||
|
|
||||||
|
## Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
|
||||||
|
```
|
||||||
|
|
||||||
|
`runtime_resources.rs` passe de la version de fichier `17` à `18`.
|
||||||
|
|
||||||
|
## Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.3.13/pre.005-fix.001.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers supprimés
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun
|
||||||
|
```
|
||||||
|
|
||||||
|
## Non-claims
|
||||||
|
|
||||||
|
Ce fix ne modifie aucune dépendance, aucun code exécutable et aucun test. Il ne change pas le comportement de `transactionSubscribe`, de l'hydration `getTransaction observed`, de la redaction runtime ni de la convergence multi-source.
|
||||||
|
|
||||||
|
## Validation dans l'environnement d'assemblage
|
||||||
|
|
||||||
|
Le toolchain Rust/Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Les commandes Cargo post-fix restent donc `NON EXÉCUTÉ LOCAL`. Les audits statiques KSP et le contrôle exhaustif du delta sont exécutés avant packaging.
|
||||||
|
|
||||||
|
## Gate opérateur requis avant pre.006
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
## Résultats statiques finaux de l'assemblage
|
||||||
|
|
||||||
|
```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, 838 files)
|
||||||
|
```
|
||||||
|
|
||||||
|
Contrôle exhaustif du delta :
|
||||||
|
|
||||||
|
```text
|
||||||
|
2 fichiers existants modifiés
|
||||||
|
1 fichier ajouté
|
||||||
|
0 fichier supprimé
|
||||||
|
aucun test modifié
|
||||||
|
aucune dépendance ajoutée/modifiée/supprimée
|
||||||
|
aucun marqueur secret-like interdit restant dans les sources Rust production du Worker
|
||||||
|
```
|
||||||
268
deltas/0.3.13/pre.005.md
Normal file
268
deltas/0.3.13/pre.005.md
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
<!-- file: deltas/0.3.13/pre.005.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta 0.3.13-pre.005 — Helius transactionSubscribe + hydration Common RAW
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison précédente : 0.3.13-pre.004-fix.001
|
||||||
|
Cargo base : 0.3.13-pre.4.fix.1
|
||||||
|
delta base : deltas/0.3.13/pre.004-fix.001.md
|
||||||
|
archive delta base : ksp-general-0.3.13-pre.004-fix.001.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur reçu le 10 septembre 2026 sur cette base est vert pour toutes les commandes exécutées : `cargo fmt`, `cargo fmt --check`, audits Rust/Markdown, `cargo check --workspace`, Clippy strict et toutes les suites de `ksp-worker-raw-transaction-ingest-lib` (`79` unit, `4` cross-layer, `11` dependency-boundary, `18` hardening, `12` public-api, `4` release-completeness, `0` doc-test). Cette exécution ne contenait pas le test Transport ni les commandes `cargo tree`; ils ne sont donc pas déclarés PASS pour ce gate précis.
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Ajouter la source Helius WebSocket prévue par la tranche sans dupliquer le chemin Common RAW :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Helius LaserStream transactionSubscribe
|
||||||
|
-> Full + Base64 + maxSupportedTransactionVersion=1 + showRewards=false
|
||||||
|
-> projection privée signature + slot + transactionIndex
|
||||||
|
-> coordinateur d'hydration source-neutral existant
|
||||||
|
-> HTTP getTransaction observed
|
||||||
|
-> Common RAW canonique
|
||||||
|
-> admission centrale Worker
|
||||||
|
-> Store
|
||||||
|
```
|
||||||
|
|
||||||
|
Le nested payload transaction Helius n'est pas utilisé comme RAW-direct. Il reste possédé par Transport et n'est jamais copié dans le signal Worker.
|
||||||
|
|
||||||
|
## Version
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison : 0.3.13-pre.005
|
||||||
|
workspace.package.version : 0.3.13-pre.5
|
||||||
|
archive : ksp-general-0.3.13-pre.005.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
`Cargo.toml` passe de la version de fichier `547` à `548`.
|
||||||
|
|
||||||
|
## Source Helius Transaction
|
||||||
|
|
||||||
|
Nouvelle surface publique Worker :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawTransactionIngestHeliusTransactionSource
|
||||||
|
RawTransactionIngestRuntimeResources::from_helius_transaction_source
|
||||||
|
RawTransactionIngestRuntimeResources::try_push_helius_transaction_source
|
||||||
|
```
|
||||||
|
|
||||||
|
Construction caller-composed :
|
||||||
|
|
||||||
|
```text
|
||||||
|
WsEndpointSettings kind helius_laserstream
|
||||||
|
HeliusTransactionSubscribeFilter
|
||||||
|
SolanaCommitment Confirmed ou Finalized
|
||||||
|
HttpTransportPool
|
||||||
|
HttpRoleName d'hydration
|
||||||
|
```
|
||||||
|
|
||||||
|
La construction est sans I/O. Elle refuse un endpoint invalide ou non Helius LaserStream, `Processed`, un réseau/provenance non représentable et l'absence d'une route HTTP `getTransaction` compatible sur le même réseau.
|
||||||
|
|
||||||
|
Au runtime, la source réutilise exclusivement les façades Transport existantes :
|
||||||
|
|
||||||
|
```text
|
||||||
|
HeliusLaserStreamWsSession::connect
|
||||||
|
HeliusLaserStreamWsSession::transaction_subscribe
|
||||||
|
HttpTransportPool::get_transaction_observed via le coordinateur commun
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun client WebSocket direct, SDK Helius, `reqwest`, `tokio-tungstenite`, backend Store ou accès Config n'est ajouté au Worker.
|
||||||
|
|
||||||
|
## Requête Helius qualifiée
|
||||||
|
|
||||||
|
La requête Worker est fixée à :
|
||||||
|
|
||||||
|
```text
|
||||||
|
commitment = Confirmed | Finalized
|
||||||
|
encoding = Base64
|
||||||
|
transactionDetails = Full
|
||||||
|
showRewards = false
|
||||||
|
maxSupportedTransactionVersion = 1
|
||||||
|
```
|
||||||
|
|
||||||
|
Une notification productive doit être `HeliusTransactionNotification::Full`. Une forme `Signature`, `Unknown` ou future malgré la requête `Full` devient `source.helius_transaction_notification_unqualified`; elle n'est jamais convertie en succès silencieux.
|
||||||
|
|
||||||
|
## Projection et hydration
|
||||||
|
|
||||||
|
Le signal privé issu d'une notification `Full` conserve seulement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
network
|
||||||
|
signature
|
||||||
|
slot
|
||||||
|
transactionIndex
|
||||||
|
safe route identity
|
||||||
|
private filter fingerprint
|
||||||
|
commitment via hydration context
|
||||||
|
```
|
||||||
|
|
||||||
|
Le nested champ `transaction` de la notification Helius n'est pas lu par `project_helius_transaction_signal` et n'est pas copié dans Worker. La construction Common RAW reste donc unique : `getTransaction observed` en Base64 avec `maxSupportedTransactionVersion = 1`, puis canonicalisation/admission existantes.
|
||||||
|
|
||||||
|
Le coordinateur d'hydration reste borné et source-neutral :
|
||||||
|
|
||||||
|
```text
|
||||||
|
in-flight hydration <= persistence_concurrency
|
||||||
|
pending source signals <= admission_queue_capacity
|
||||||
|
coalescence key = network + signature + commitment
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette voie converge avec Yellowstone et Standard Logs au lieu d'introduire un second moteur HTTP.
|
||||||
|
|
||||||
|
## Identité logique, redaction et secrets
|
||||||
|
|
||||||
|
Le `source_key` Helius est dérivé de :
|
||||||
|
|
||||||
|
```text
|
||||||
|
domain KSP live-source v1
|
||||||
|
family = helius_transaction
|
||||||
|
network
|
||||||
|
safe provider identity
|
||||||
|
safe endpoint identity
|
||||||
|
commitment
|
||||||
|
private normalized Helius filter fingerprint
|
||||||
|
SHA-256 -> [u8; 32]
|
||||||
|
```
|
||||||
|
|
||||||
|
L'empreinte privée couvre `vote`, `failed`, signature exacte, listes `accountInclude`/`accountExclude`/`accountRequired` et `tokenAccounts`. Les listes de pubkeys sont triées avant hash afin que leur ordre ne crée pas artificiellement deux identités logiques. Le rôle/pool HTTP d'hydration reste exclu du `source_key`.
|
||||||
|
|
||||||
|
Les URLs et credentials restent encapsulés par les settings Transport privés utilisés pour ouvrir les connexions ; ils ne sont exposés ni par l'API publique ni par `Debug`. Les valeurs de signature/pubkeys du filtre et les octets de `source_key` ne sont pas rendus dans les diagnostics Worker.
|
||||||
|
|
||||||
|
Les profils Config Helius et `KSP_SECRET_HELIUS_API_KEY` existaient déjà avant cette tranche. Aucun changement Config, `.env`, entitlement, quota, prix ou tier provider n'est codé dans Worker.
|
||||||
|
|
||||||
|
## Activation runtime
|
||||||
|
|
||||||
|
Le contrat reste volontairement mono-source avant `pre.007` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
1 source Yellowstone : productif
|
||||||
|
1 source Standard Logs : productif
|
||||||
|
1 source Standard Block : productif
|
||||||
|
1 source Helius Transaction : productif
|
||||||
|
2..32 sources : composition validable
|
||||||
|
start 2..32 : runtime_resources.multi_source_activation_pending avant spawn
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune source configurée n'est silencieusement ignorée.
|
||||||
|
|
||||||
|
## Preuves ajoutées
|
||||||
|
|
||||||
|
Unit tests Worker :
|
||||||
|
|
||||||
|
```text
|
||||||
|
validation protocole/commitment/route HTTP Helius
|
||||||
|
requête fixe Full/Base64/maxV1/showRewards=false
|
||||||
|
source_key filter-sensitive, order-normalized et hydration-role-neutral
|
||||||
|
Debug redacted : signature/pubkeys/URL/API key/source-key absents
|
||||||
|
projection Full -> signature/slot/index uniquement
|
||||||
|
Helius reference -> un getTransaction observed -> Common RAW exact
|
||||||
|
provenance helius_ws_http + transaction_get_transaction
|
||||||
|
rejet transactionnel d'une identité Helius dupliquée
|
||||||
|
```
|
||||||
|
|
||||||
|
Canaris externes :
|
||||||
|
|
||||||
|
```text
|
||||||
|
public_api : construction/from/push depuis crate root sans escape hatch des ressources internes
|
||||||
|
dependency_boundary : façades Transport Helius + un seul getTransaction commun
|
||||||
|
hardening : nested payload non copié, secrets/redaction et absence de tier provider
|
||||||
|
release_completeness : export et canaris pre.005 obligatoires
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation corrigée
|
||||||
|
|
||||||
|
La documentation Worker est mise à jour pour inclure la source Helius et la convergence d'hydration. Une mention héritée de `README.md` qui classait encore `blockSubscribe` et Helius parmi les sources hors périmètre est supprimée ; `USAGE.md` reste volontairement version-neutral.
|
||||||
|
|
||||||
|
## Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/README.md
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/USAGE.md
|
||||||
|
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/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
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
|
||||||
|
docs/plans/034-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE_PLAN.md
|
||||||
|
docs/validation/030-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.3.13/pre.005.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers supprimés
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun
|
||||||
|
```
|
||||||
|
|
||||||
|
## Non-claims
|
||||||
|
|
||||||
|
`pre.005` ne prétend pas :
|
||||||
|
|
||||||
|
```text
|
||||||
|
supervision simultanée de plusieurs sources
|
||||||
|
HTTP live block polling
|
||||||
|
RAW-direct depuis le nested payload Helius
|
||||||
|
SDK Helius dans Worker
|
||||||
|
secret/API key lu directement par Worker
|
||||||
|
nouveau profil Config Helius
|
||||||
|
entitlement/tier/quota/prix provider codé
|
||||||
|
modification de ksp-onchain-transport-lib
|
||||||
|
nouvelle dépendance externe
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation dans l'environnement d'assemblage
|
||||||
|
|
||||||
|
Le toolchain Rust/Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Les commandes Cargo post-`pre.005` restent donc `NON EXÉCUTÉ LOCAL`. Les audits statiques KSP, le scan des identifiants normatifs et le contrôle exhaustif du diff sont exécutés avant packaging.
|
||||||
|
|
||||||
|
## Gate opérateur requis avant pre.006
|
||||||
|
|
||||||
|
```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
|
||||||
|
cargo test -p ksp-worker-raw-transaction-ingest-lib
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
`pre.006` ne doit pas commencer si ce gate révèle une anomalie.
|
||||||
|
|
||||||
|
## Résultats statiques finaux de l'assemblage
|
||||||
|
|
||||||
|
```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, 837 files)
|
||||||
|
Normative rule definitions: 489
|
||||||
|
Unique normative IDs: 489
|
||||||
|
Duplicates: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contrôle exhaustif avant packaging
|
||||||
|
|
||||||
|
```text
|
||||||
|
12 fichiers existants modifiés
|
||||||
|
1 fichier ajouté
|
||||||
|
0 fichier supprimé
|
||||||
|
12/12 headers de fichiers existants incrémentés exactement de +1
|
||||||
|
Cargo.toml : version workspace uniquement hors header
|
||||||
|
aucune dépendance ajoutée/modifiée/supprimée
|
||||||
|
```
|
||||||
111
deltas/0.3.13/pre.006-fix.001.md
Normal file
111
deltas/0.3.13/pre.006-fix.001.md
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
<!-- file: deltas/0.3.13/pre.006-fix.001.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta 0.3.13-pre.006-fix.001 — correction du canari historique après live block discovery
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison précédente : 0.3.13-pre.006
|
||||||
|
Cargo base : 0.3.13-pre.6
|
||||||
|
delta base : deltas/0.3.13/pre.006.md
|
||||||
|
archive delta base : ksp-general-0.3.13-pre.006.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur du 10 septembre 2026 sur `pre.006` est vert pour `cargo fmt`, `cargo fmt --check`, les audits Rust/Markdown, `cargo check --workspace`, Clippy strict, les `88` tests unitaires Worker et les `13` tests `dependency_boundary`. Il s'arrête ensuite sur un seul canari de hardening : `pre_010_production_surface_has_no_historical_backfill_or_retriever_contract` détecte la chaîne `Backfill` dans la Rustdoc de la nouvelle source HTTP live block polling.
|
||||||
|
|
||||||
|
La même vérification interdisait également globalement `Discovery`/`discovery`. Cette hypothèse n'est plus valide depuis `pre.006` : la découverte run-local via `getBlocksWithLimit` est une primitive légitime du live polling et ne constitue ni un retriever historique ni un Backfill.
|
||||||
|
|
||||||
|
## Objectif du fix
|
||||||
|
|
||||||
|
Conserver la séparation Worker/Backfill sans interdire la terminologie de live block discovery introduite par le poller HTTP :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Rustdoc HTTP polling : suppression de la mention Backfill
|
||||||
|
canari historique : conserve les interdictions retriever/backfill/checkpoint/historical
|
||||||
|
mais ne classe plus Discovery/discovery comme marqueur historique
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune logique de polling, cadence, Transport, admission, Store, processing frontier, provenance, source identity ou API publique n'est modifiée.
|
||||||
|
|
||||||
|
## Version
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison : 0.3.13-pre.006-fix.001
|
||||||
|
workspace.package.version : 0.3.13-pre.6.fix.1
|
||||||
|
archive : ksp-general-0.3.13-pre.006-fix.001.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
`Cargo.toml` passe de la version de fichier `550` à `551`.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
```
|
||||||
|
|
||||||
|
Versions de fichiers :
|
||||||
|
|
||||||
|
```text
|
||||||
|
runtime_resources.rs : 19 -> 20
|
||||||
|
hardening.rs : 15 -> 16
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.3.13/pre.006-fix.001.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers supprimés
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun
|
||||||
|
```
|
||||||
|
|
||||||
|
## Non-claims
|
||||||
|
|
||||||
|
Ce fix ne modifie aucune dépendance et n'ajoute aucune source. Il ne change ni la borne de run, ni `getSlot`, ni `getBlocksWithLimit`, ni `getBlock observed`, ni les règles Legacy/V0/V1, ni la sémantique `block:null`.
|
||||||
|
|
||||||
|
## Validation dans l'environnement d'assemblage
|
||||||
|
|
||||||
|
Le toolchain Rust/Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Les commandes Cargo post-fix restent donc `NON EXÉCUTÉ LOCAL`. Les audits statiques KSP et le contrôle exhaustif du delta sont exécutés avant packaging.
|
||||||
|
|
||||||
|
## Gate opérateur requis avant pre.007
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
## Résultats statiques finaux de l'assemblage
|
||||||
|
|
||||||
|
```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, 840 files)
|
||||||
|
Normative rule definitions: 489
|
||||||
|
Unique normative IDs: 489
|
||||||
|
Duplicates: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
Contrôle ciblé de la surface production Worker :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-worker-raw-retriever : 0
|
||||||
|
raw_transaction_retriever : 0
|
||||||
|
RawTransactionRetriever : 0
|
||||||
|
Backfill/backfill : 0
|
||||||
|
Checkpoint/checkpoint : 0
|
||||||
|
historical : 0
|
||||||
|
```
|
||||||
|
|
||||||
|
`Discovery`/`discovery` n'est plus un marqueur interdit global : `pre.006` l'emploie pour la découverte live run-local de slots/blocs par `getBlocksWithLimit`.
|
||||||
322
deltas/0.3.13/pre.006.md
Normal file
322
deltas/0.3.13/pre.006.md
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
<!-- file: deltas/0.3.13/pre.006.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta 0.3.13-pre.006 — HTTP live block polling run-local
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison précédente : 0.3.13-pre.005-fix.001
|
||||||
|
Cargo base : 0.3.13-pre.5.fix.1
|
||||||
|
delta base : deltas/0.3.13/pre.005-fix.001.md
|
||||||
|
archive delta base : ksp-general-0.3.13-pre.005-fix.001.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur reçu le 10 septembre 2026 sur cette base est entièrement vert pour les commandes exécutées : `cargo fmt`, `cargo fmt --check`, audits Rust/Markdown, `cargo check --workspace`, Clippy strict et toutes les suites de `ksp-worker-raw-transaction-ingest-lib` (`84` unit, `4` cross-layer, `12` dependency-boundary, `19` hardening, `13` public-api, `4` release-completeness, `0` doc-test). Cette exécution ne contenait ni le test Transport ni les commandes `cargo tree`; ils ne sont donc pas déclarés PASS pour ce gate précis.
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Ajouter la source HTTP live prévue par la tranche sans la transformer en Backfill :
|
||||||
|
|
||||||
|
```text
|
||||||
|
getSlot(commitment)
|
||||||
|
-> borne inférieure du run
|
||||||
|
getBlocksWithLimit(next_scan_slot, bounded_limit, commitment)
|
||||||
|
-> découverte des blocs disponibles
|
||||||
|
getBlock observed(slot, Full/Base64/maxV1/rewards=false)
|
||||||
|
-> Common RAW direct Legacy/V0/V1
|
||||||
|
-> admission centrale Worker
|
||||||
|
-> Store
|
||||||
|
```
|
||||||
|
|
||||||
|
Le polling ne remonte jamais avant la borne observée au démarrage et ne crée aucun checkpoint durable inter-run.
|
||||||
|
|
||||||
|
## Version
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison : 0.3.13-pre.006
|
||||||
|
workspace.package.version : 0.3.13-pre.6
|
||||||
|
archive : ksp-general-0.3.13-pre.006.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
`Cargo.toml` passe également de la version de fichier `549` à `550`.
|
||||||
|
|
||||||
|
## Source HTTP Block Polling
|
||||||
|
|
||||||
|
Nouvelle surface publique Worker :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawTransactionIngestHttpBlockPollingSource
|
||||||
|
RawTransactionIngestRuntimeResources::from_http_block_polling_source
|
||||||
|
RawTransactionIngestRuntimeResources::try_push_http_block_polling_source
|
||||||
|
DEFAULT_RAW_TRANSACTION_INGEST_HTTP_POLL_INTERVAL
|
||||||
|
MIN_RAW_TRANSACTION_INGEST_HTTP_POLL_INTERVAL
|
||||||
|
MAX_RAW_TRANSACTION_INGEST_HTTP_POLL_INTERVAL
|
||||||
|
DEFAULT_RAW_TRANSACTION_INGEST_HTTP_POLL_MAX_BLOCKS_PER_CYCLE
|
||||||
|
MIN_RAW_TRANSACTION_INGEST_HTTP_POLL_MAX_BLOCKS_PER_CYCLE
|
||||||
|
MAX_RAW_TRANSACTION_INGEST_HTTP_POLL_MAX_BLOCKS_PER_CYCLE
|
||||||
|
```
|
||||||
|
|
||||||
|
La construction caller-owned reçoit un `HttpTransportPool`, un `HttpRoleName` et un commitment `Confirmed` ou `Finalized`. Elle est sans I/O et vérifie statiquement que le rôle possède des routes compatibles `getSlot`, `getBlocksWithLimit` et `getBlock` sur un même réseau.
|
||||||
|
|
||||||
|
Bornes publiques :
|
||||||
|
|
||||||
|
```text
|
||||||
|
poll interval : défaut 1 s ; min 100 ms ; max 30 s
|
||||||
|
max discovered blocks/cycle : défaut 128 ; min 1 ; max 1024
|
||||||
|
```
|
||||||
|
|
||||||
|
Les limitations physiques, la sélection d'endpoint, le rate-limit, les retries et le backoff HTTP restent possédés par `ksp-onchain-transport-lib`.
|
||||||
|
|
||||||
|
## Borne de run et progression
|
||||||
|
|
||||||
|
Au démarrage :
|
||||||
|
|
||||||
|
```text
|
||||||
|
start_slot = getSlot(commitment)
|
||||||
|
next_scan_slot = start_slot
|
||||||
|
```
|
||||||
|
|
||||||
|
Le Worker ne demande aucun slot inférieur à `start_slot`. Pour chaque cycle, il relit le tip courant, puis appelle `getBlocksWithLimit` depuis `next_scan_slot`. La réponse doit être strictement croissante et ne peut contenir aucun slot inférieur à la frontier de scan.
|
||||||
|
|
||||||
|
Chaque slot listé est matérialisé par `getBlock observed`. Si le résultat vaut `null` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
next_scan_slot = slot
|
||||||
|
aucun settled
|
||||||
|
aucun Common RAW
|
||||||
|
reprise au cycle suivant
|
||||||
|
```
|
||||||
|
|
||||||
|
Le cas `null` n'est donc ni assimilé à un bloc vide ni converti en progression silencieuse.
|
||||||
|
|
||||||
|
Les slots non listés par `getBlocksWithLimit` sont traités comme non produits/skipped dans le run courant. Lorsque la liste reçue ne remplit pas le batch demandé et qu'aucun slot n'est bloqué par `null`, la frontier peut rejoindre le tip observé du cycle sans lancer de scan historique implicite.
|
||||||
|
|
||||||
|
## RAW-direct et version
|
||||||
|
|
||||||
|
Le poller demande exactement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
encoding = Base64
|
||||||
|
transactionDetails = Full
|
||||||
|
maxSupportedTransactionVersion = 1
|
||||||
|
showRewards = false
|
||||||
|
commitment = Confirmed | Finalized
|
||||||
|
```
|
||||||
|
|
||||||
|
Chaque transaction qualifiée produit directement `RawTransactionMaterial` avec :
|
||||||
|
|
||||||
|
```text
|
||||||
|
signature embarquée dans le wire Base64
|
||||||
|
slot
|
||||||
|
blockTime
|
||||||
|
meta
|
||||||
|
version Legacy | 0 | 1
|
||||||
|
transactionIndex dérivé de la position dans le bloc
|
||||||
|
```
|
||||||
|
|
||||||
|
Les cas suivants sont fail-closed : transaction non Base64, transactions omises/nulles, version omise/nulle, version `>1`, signature invalide ou index non représentable en `u32`.
|
||||||
|
|
||||||
|
La voie HTTP Block Polling est RAW-direct et n'utilise pas le coordinateur d'hydration. Le slot n'est marqué `settled` qu'après l'envoi réussi de toutes les transactions du bloc vers l'admission centrale ; `hydration_pending` n'est jamais artificiellement incrémenté.
|
||||||
|
|
||||||
|
## Provenance observée
|
||||||
|
|
||||||
|
La provenance de chaque transaction reprend l'endpoint et le provider réellement gagnants de `getBlock observed` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
origin = Live
|
||||||
|
protocol = solana_http
|
||||||
|
method = block_polling_get_block
|
||||||
|
endpoint = endpoint observé
|
||||||
|
provider = provider observé
|
||||||
|
commitment = source commitment
|
||||||
|
capture session = WorkerId
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun URL, header HTTP ou corps brut n'est exposé par `HttpObservedValue` ou les diagnostics Worker.
|
||||||
|
|
||||||
|
## Identité logique
|
||||||
|
|
||||||
|
Le `source_key` HTTP polling est dérivé de :
|
||||||
|
|
||||||
|
```text
|
||||||
|
domain KSP live-source v1
|
||||||
|
family = http_block_polling
|
||||||
|
network
|
||||||
|
polling role
|
||||||
|
commitment
|
||||||
|
private route-profile fingerprint
|
||||||
|
SHA-256 -> [u8; 32]
|
||||||
|
```
|
||||||
|
|
||||||
|
L'empreinte de profil est construite à partir des identités sûres cluster/provider/endpoint et des capacités pertinentes de chaque route active, après tri déterministe. La cadence et le batch de découverte n'entrent volontairement pas dans l'identité logique : modifier un réglage de timing ne crée pas une deuxième source conceptuelle.
|
||||||
|
|
||||||
|
Le bound global `1..32`, l'invariant réseau unique et le rejet transactionnel des doublons de `pre.002` s'appliquent à cette famille.
|
||||||
|
|
||||||
|
## Activation runtime
|
||||||
|
|
||||||
|
Le contrat reste volontairement mono-source avant `pre.007` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
1 source Yellowstone : productif
|
||||||
|
1 source Standard Logs : productif
|
||||||
|
1 source Standard Block : productif
|
||||||
|
1 source Helius Transaction : productif
|
||||||
|
1 source HTTP Block Polling : productif
|
||||||
|
2..32 sources : composition validable
|
||||||
|
start 2..32 : runtime_resources.multi_source_activation_pending avant spawn
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune source configurée n'est ignorée silencieusement.
|
||||||
|
|
||||||
|
## Preuves ajoutées
|
||||||
|
|
||||||
|
Unit tests Worker :
|
||||||
|
|
||||||
|
```text
|
||||||
|
bornes cadence/batch et commitment
|
||||||
|
présence exacte des capacités HTTP requises
|
||||||
|
source_key stable malgré changement de timing et différent si route logique différente
|
||||||
|
redaction URL sensible dans Debug
|
||||||
|
validation stricte de la découverte croissante
|
||||||
|
qualification Legacy/V0/V1 et rejet version ambiguë/future
|
||||||
|
rejet transactionnel d'une identité HTTP polling dupliquée
|
||||||
|
```
|
||||||
|
|
||||||
|
Canaris externes :
|
||||||
|
|
||||||
|
```text
|
||||||
|
public_api : constructeurs/from/push et bornes publiques disponibles depuis crate root
|
||||||
|
dependency_boundary : Transport facade uniquement, aucun Config/Backfill/backend/direct HTTP client
|
||||||
|
hardening : run-local, timer borné, getBlock observed unique, aucun task par tick, aucun hydration_pending direct
|
||||||
|
release_completeness : exports et canaris pre.006 obligatoires
|
||||||
|
```
|
||||||
|
|
||||||
|
Les anciens canaris qui interdisaient globalement `get_block_observed` ou `tokio::time::sleep` sont resserrés sur le coordinateur d'hydration : ces primitives restent interdites dans l'hydration mais sont désormais légitimes, explicites et bornées dans la seule source HTTP polling.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
`README.md`, le README du crate, `USAGE.md`, le plan et le journal de validation sont alignés sur la cinquième famille productive. `USAGE.md` reste version-neutral : il décrit la construction et le comportement sans journaliser le numéro de prerelease.
|
||||||
|
|
||||||
|
Une incohérence du README de crate, qui classait encore le live HTTP polling parmi les éléments hors périmètre, est corrigée dans cette tranche.
|
||||||
|
|
||||||
|
## Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
README.md
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/README.md
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/USAGE.md
|
||||||
|
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/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
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
|
||||||
|
docs/plans/034-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE_PLAN.md
|
||||||
|
docs/validation/030-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.3.13/pre.006.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers supprimés
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun
|
||||||
|
```
|
||||||
|
|
||||||
|
## Non-claims
|
||||||
|
|
||||||
|
`pre.006` ne prétend pas :
|
||||||
|
|
||||||
|
```text
|
||||||
|
supervision simultanée de plusieurs sources
|
||||||
|
checkpoint durable du polling
|
||||||
|
Backfill implicite
|
||||||
|
scan de slots antérieurs au start_slot du run
|
||||||
|
retry/backoff HTTP implémenté dans Worker
|
||||||
|
client reqwest direct dans Worker
|
||||||
|
Config lu dans Worker
|
||||||
|
getBlock=null assimilé à un bloc vide
|
||||||
|
transaction version >1 ou ambiguë acceptée
|
||||||
|
modification de ksp-onchain-transport-lib
|
||||||
|
nouvelle dépendance externe
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation dans l'environnement d'assemblage
|
||||||
|
|
||||||
|
Le toolchain Rust/Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Les commandes Cargo post-`pre.006` restent donc `NON EXÉCUTÉ LOCAL`. Les audits statiques KSP, le scan normatif, le contrôle des versions de fichiers et le diff exhaustif sont exécutés avant packaging.
|
||||||
|
|
||||||
|
## Gate opérateur requis avant pre.007
|
||||||
|
|
||||||
|
```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
|
||||||
|
cargo test -p ksp-worker-raw-transaction-ingest-lib
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
`pre.007` ne doit pas commencer si ce gate révèle une anomalie.
|
||||||
|
|
||||||
|
## Résultats statiques finaux de l'assemblage
|
||||||
|
|
||||||
|
Les valeurs ci-dessous sont mises à jour après la dernière passe d'audits sur l'état exact livré.
|
||||||
|
|
||||||
|
```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, 839 files)
|
||||||
|
Normative rule definitions: 489
|
||||||
|
Unique normative IDs: 489
|
||||||
|
Duplicates: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
## Contrôle exhaustif avant packaging
|
||||||
|
|
||||||
|
Comparaison avec la base `0.3.13-pre.005-fix.001` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
added : 1
|
||||||
|
deltas/0.3.13/pre.006.md
|
||||||
|
modified : 13
|
||||||
|
Cargo.toml
|
||||||
|
README.md
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/README.md
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/USAGE.md
|
||||||
|
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/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
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
|
||||||
|
docs/plans/034-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE_PLAN.md
|
||||||
|
docs/validation/030-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE.md
|
||||||
|
deleted : 0
|
||||||
|
```
|
||||||
|
|
||||||
|
Les `13/13` fichiers existants modifiés portent une version de fichier exactement `+1`. Le diff de `Cargo.toml` est limité au header `549 -> 550` et à `workspace.package.version = "0.3.13-pre.6"`; aucun membre workspace ni aucune dépendance n'est modifié.
|
||||||
|
|
||||||
|
La revue statique ciblée confirme dans le code Worker :
|
||||||
|
|
||||||
|
```text
|
||||||
|
get_block_observed( : 1 occurrence, source HTTP polling uniquement
|
||||||
|
get_blocks_with_limit( : 1 occurrence, source HTTP polling uniquement
|
||||||
|
tokio::time::sleep : 1 occurrence, cadence du poller uniquement
|
||||||
|
aucun reqwest/tokio-tungstenite/tonic/yellowstone-grpc-proto direct
|
||||||
|
aucun ksp-config-lib/ksp-job-backfill-lib/ksp-store-postgres-lib direct
|
||||||
|
aucun unbounded_channel
|
||||||
|
```
|
||||||
218
deltas/0.3.13/pre.007.md
Normal file
218
deltas/0.3.13/pre.007.md
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
<!-- file: deltas/0.3.13/pre.007.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta 0.3.13-pre.007 — supervision et inventaire multi-source
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison précédente : 0.3.13-pre.006-fix.001
|
||||||
|
Cargo base : 0.3.13-pre.6.fix.1
|
||||||
|
delta base : deltas/0.3.13/pre.006-fix.001.md
|
||||||
|
archive delta base : ksp-general-0.3.13-pre.006-fix.001.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur communiqué le 10 septembre 2026 est vert sur toutes les commandes exécutées : `cargo fmt`, `cargo fmt --check`, audits Rust/Markdown, `cargo check --workspace`, Clippy strict et toutes les suites de `ksp-worker-raw-transaction-ingest-lib` (`88` unit, `4` cross-layer, `13` dependency-boundary, `20` hardening, `14` public-api, `4` release-completeness, `0` doc-test). Le log reçu ne contient pas les sorties séparées `cargo test -p ksp-onchain-transport-lib` ni les trois commandes `cargo tree`; elles ne sont donc pas déclarées PASS dans ce delta.
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Activer réellement la collection `1..32` déjà composable depuis `pre.002` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawTransactionIngestRuntimeResources
|
||||||
|
-> validation complète réseau / doublons / borne
|
||||||
|
-> spawn de toutes les sources configurées
|
||||||
|
-> inventaire privé source_key -> latest state/frontier
|
||||||
|
-> projection frontier source-neutral agrégée
|
||||||
|
-> terminalité conservatrice si une source disparaît ou échoue
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun provider n'est traité comme primaire, fallback ou standby.
|
||||||
|
|
||||||
|
## Version
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison : 0.3.13-pre.007
|
||||||
|
workspace.package.version : 0.3.13-pre.7
|
||||||
|
archive : ksp-general-0.3.13-pre.007.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
`Cargo.toml` passe de la version de fichier `551` à `552`.
|
||||||
|
|
||||||
|
## Supervisor multi-source
|
||||||
|
|
||||||
|
`RawTransactionIngestWorker::start_with_runtime_resources` ne vérifie plus `validate_single_source_activation`. Après validation du réseau, le runtime délègue à `RawTransactionIngestRuntimeResources::run_live_sources`.
|
||||||
|
|
||||||
|
Le supervisor privé :
|
||||||
|
|
||||||
|
```text
|
||||||
|
crée un stop channel source-owned
|
||||||
|
crée un JoinSet borné par la collection 1..32
|
||||||
|
spawn chaque source configurée exactement une fois
|
||||||
|
clone uniquement les handles nécessaires
|
||||||
|
rejoint toutes les tasks avant de rendre son résultat
|
||||||
|
```
|
||||||
|
|
||||||
|
La vieille erreur `runtime_resources.multi_source_activation_pending` disparaît du code production.
|
||||||
|
|
||||||
|
## Terminalité conservatrice
|
||||||
|
|
||||||
|
La politique reste fail-closed :
|
||||||
|
|
||||||
|
```text
|
||||||
|
stop Worker
|
||||||
|
-> fanout stop vers toutes les sources
|
||||||
|
-> join complet
|
||||||
|
-> Ok seulement si les sources ferment sans erreur
|
||||||
|
|
||||||
|
source Err
|
||||||
|
-> stop immédiat des autres sources
|
||||||
|
-> join complet
|
||||||
|
-> faute source terminale
|
||||||
|
|
||||||
|
source Ok avant stop Worker
|
||||||
|
-> fermeture inattendue d'une source configurée
|
||||||
|
-> stop des autres sources
|
||||||
|
-> faute terminale
|
||||||
|
|
||||||
|
JoinError
|
||||||
|
-> faute terminale sûre
|
||||||
|
```
|
||||||
|
|
||||||
|
Le Worker ne suppose pas qu'une autre source couvre les mêmes filtres, le même provider universe ou la même complétude.
|
||||||
|
|
||||||
|
## Inventaire privé et processing frontier
|
||||||
|
|
||||||
|
`RawTransactionIngestSourceInventory` et `RawTransactionIngestSourceInventoryPublisher` restent privés au module runtime resources. L'inventaire est initialisé depuis les `source_key` déjà validées et possède exactement une entrée latest-value par source du run.
|
||||||
|
|
||||||
|
Chaque source conserve son propre publisher local. Un wrapper privé reporte ses projections dans l'inventaire puis publie une projection globale conservative :
|
||||||
|
|
||||||
|
```text
|
||||||
|
hydration_pending : somme saturante temporaire des sources
|
||||||
|
oldest_pending_slot : minimum global
|
||||||
|
processing_frontier_slot : None si une source n'a pas encore de frontier
|
||||||
|
sinon minimum de toutes les frontiers
|
||||||
|
source_state : Failed > Reconnecting > Closing > Active > Closed/None
|
||||||
|
reconnect/replay/gap : agrégation saturante, jamais wrap arithmétique
|
||||||
|
```
|
||||||
|
|
||||||
|
Le choix du minimum n'est pas une preuve de coverage blockchain. La frontier reste run-local et source-neutral.
|
||||||
|
|
||||||
|
## Propriété d'ownership
|
||||||
|
|
||||||
|
Les sources physiques continuent d'appartenir à leurs façades Transport existantes. Le Worker ne crée ni client reqwest, ni socket alternatif, ni SDK provider. Le supervisor ajoute uniquement l'ownership des tasks source et de leur lifecycle commun.
|
||||||
|
|
||||||
|
Une source qui termine avant le stop est explicitement marquée `Closed` ou `Failed` dans l'inventaire avant le résultat du supervisor. Les identités `source_key` restent privées et ne sont jamais projetées dans `RawTransactionIngestSnapshot` ou le crate root.
|
||||||
|
|
||||||
|
## Preuves ajoutées
|
||||||
|
|
||||||
|
Unit tests déterministes :
|
||||||
|
|
||||||
|
```text
|
||||||
|
inventaire 2 sources : pending/frontier/state/counters agrégés conservativement
|
||||||
|
stop supervisor : trois tasks source actives reçoivent le stop et sont toutes jointes
|
||||||
|
source failure : la source sœur reçoit le stop et est jointe avant retour de faute
|
||||||
|
```
|
||||||
|
|
||||||
|
Canaris externes :
|
||||||
|
|
||||||
|
```text
|
||||||
|
dependency_boundary : run_live_sources, JoinSet, inventaire privé, vieille barrière absente
|
||||||
|
hardening : stop/fault/join fail-closed, aucune policy primary/standby, aucun canal unbounded
|
||||||
|
public_api : inventaire/live-source/source_key non exportés
|
||||||
|
```
|
||||||
|
|
||||||
|
Les canaris historiques `0.3.12` qui cherchaient `run_single_live_source` sont mis à jour vers le supervisor multi-source au lieu d'être supprimés.
|
||||||
|
|
||||||
|
## Frontière de tranche
|
||||||
|
|
||||||
|
`pre.007` active la simultanéité, mais ne ferme pas encore la convergence transactionnelle cross-source :
|
||||||
|
|
||||||
|
```text
|
||||||
|
pas de coalescence globale de la même signature entre plusieurs sources
|
||||||
|
pas d'unique hydration cross-source garantie
|
||||||
|
pas de fusion de plusieurs provenance/observation seeds vers une acquisition canonique unique
|
||||||
|
pas encore de record_raw_transaction_observation pour les observations supplémentaires convergées
|
||||||
|
pas encore de fairness durcie duplicate-storm
|
||||||
|
pas encore de source_total/source_active/source_failed publics
|
||||||
|
```
|
||||||
|
|
||||||
|
Les coordinators d'hydration des sources reference-bearing restent donc source-local pendant cette tranche. La fermeture de la coalescence globale et des observations multiples appartient à `pre.008`, puis les bornes/fairness renforcées à `pre.009`.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Le README du crate décrit désormais le supervisor 1..32, la terminalité conservative et la frontier agrégée. `USAGE.md` reste version-neutral et montre comment composer plusieurs sources via les `try_push_*` existants sans mentionner le numéro de prerelease.
|
||||||
|
|
||||||
|
Le plan et la validation consignent le gate opérateur réellement reçu et distinguent explicitement ce qui reste reporté à `pre.008+`.
|
||||||
|
|
||||||
|
## Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/README.md
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/USAGE.md
|
||||||
|
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/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
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
|
||||||
|
docs/plans/034-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE_PLAN.md
|
||||||
|
docs/validation/030-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.3.13/pre.007.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers supprimés
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation dans l'environnement d'assemblage
|
||||||
|
|
||||||
|
Le toolchain Rust/Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Les commandes Cargo post-`pre.007` restent donc `NON EXÉCUTÉ LOCAL`. Les audits statiques KSP, le scan normatif, les versions de fichiers et le diff exhaustif sont exécutés sur l'état exact avant packaging.
|
||||||
|
|
||||||
|
## Résultats statiques finaux de l'assemblage
|
||||||
|
|
||||||
|
```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, 841 files)
|
||||||
|
Normative rule definitions: 489
|
||||||
|
Unique normative IDs: 489
|
||||||
|
Duplicates: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
Contrôle ciblé du supervisor :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ancienne barrière validate_single_source_activation : absente
|
||||||
|
runtime_resources.multi_source_activation_pending : absent du code production
|
||||||
|
inventaire source public : absent
|
||||||
|
source_key public : absent
|
||||||
|
unbounded_channel : absent
|
||||||
|
nouvelle dépendance : aucune
|
||||||
|
```
|
||||||
|
|
||||||
|
## Gate opérateur requis avant pre.008
|
||||||
|
|
||||||
|
```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
|
||||||
|
cargo test -p ksp-worker-raw-transaction-ingest-lib
|
||||||
|
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
|
||||||
|
```
|
||||||
85
deltas/0.3.13/pre.008-fix.001.md
Normal file
85
deltas/0.3.13/pre.008-fix.001.md
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<!-- file: deltas/0.3.13/pre.008-fix.001.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta 0.3.13-pre.008-fix.001 — correction compilation convergence persistence
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison précédente : 0.3.13-pre.008
|
||||||
|
Cargo base : 0.3.13-pre.8
|
||||||
|
delta base : deltas/0.3.13/pre.008.md
|
||||||
|
archive delta base : ksp-general-0.3.13-pre.008.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur du 10 septembre 2026 sur `pre.008` est vert pour `cargo fmt`, `cargo fmt --check` et les audits Rust/Markdown. `cargo check --workspace` met ensuite en évidence deux défauts de compilation dans `ksp-worker-raw-transaction-ingest-lib` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
unused import : pub(crate) use self::persistence::persist_raw_transaction_ingest_acquisition
|
||||||
|
E0004 : match non exhaustif sur RawObservationWriteOutcome #[non_exhaustive]
|
||||||
|
```
|
||||||
|
|
||||||
|
Clippy strict reproduit les deux défauts et les tests ne peuvent pas être compilés avant leur correction.
|
||||||
|
|
||||||
|
## Objectif du fix
|
||||||
|
|
||||||
|
Corriger strictement ces deux défauts sans modifier le contrat de convergence `pre.008` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
re-export crate-root : conservé conformément aux règles KSP
|
||||||
|
appels internes : via crate::persist_raw_transaction_ingest_acquisition(...)
|
||||||
|
non-exhaustive enum : wildcard fail-closed vers une erreur runtime stable
|
||||||
|
```
|
||||||
|
|
||||||
|
Le wildcard ne transforme aucune variante future en succès implicite. Toute variante `RawObservationWriteOutcome` inconnue est rejetée comme `persistence.additional_observation_outcome_invalid`.
|
||||||
|
|
||||||
|
## Version
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison : 0.3.13-pre.008-fix.001
|
||||||
|
workspace.package.version : 0.3.13-pre.8.fix.1
|
||||||
|
archive : ksp-general-0.3.13-pre.008-fix.001.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
`Cargo.toml` passe de la version de fichier `553` à `554`.
|
||||||
|
|
||||||
|
## Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/src/persistence.rs
|
||||||
|
```
|
||||||
|
|
||||||
|
`persistence.rs` passe de la version de fichier `2` à `3`.
|
||||||
|
|
||||||
|
## Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.3.13/pre.008-fix.001.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers supprimés
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun
|
||||||
|
```
|
||||||
|
|
||||||
|
## Non-claims
|
||||||
|
|
||||||
|
Ce fix ne modifie aucune dépendance, aucune API publique, aucun cache de convergence, aucune clé de coalescence, aucune sémantique d'hydration, aucune observation/provenance et aucune règle Store de première acquisition ou d'observation supplémentaire.
|
||||||
|
|
||||||
|
## Validation dans l'environnement d'assemblage
|
||||||
|
|
||||||
|
Le toolchain Rust/Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Les commandes Cargo post-fix restent donc `NON EXÉCUTÉ LOCAL`. Les audits statiques KSP et le contrôle exhaustif du delta sont exécutés avant packaging.
|
||||||
|
|
||||||
|
## Gate opérateur requis avant pre.009
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
89
deltas/0.3.13/pre.008-fix.002.md
Normal file
89
deltas/0.3.13/pre.008-fix.002.md
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
<!-- file: deltas/0.3.13/pre.008-fix.002.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta 0.3.13-pre.008-fix.002 — correction visibilité hydration globale et constructeur de test
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison précédente : 0.3.13-pre.008-fix.001
|
||||||
|
Cargo base : 0.3.13-pre.8.fix.1
|
||||||
|
delta base : deltas/0.3.13/pre.008-fix.001.md
|
||||||
|
archive delta base : ksp-general-0.3.13-pre.008-fix.001.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur du 10 septembre 2026 confirme que `cargo fmt`, `cargo fmt --check`, les audits Rust/Markdown et `cargo check --workspace` passent. `cargo check` signale toutefois quatre warnings dans le Worker, puis Clippy strict les transforme en erreurs :
|
||||||
|
|
||||||
|
```text
|
||||||
|
3 x private_interfaces : trois méthodes pub(crate) run exposent RawTransactionIngestGlobalHydrationRegistry, qui reste privée au module
|
||||||
|
1 x dead_code : RawTransactionIngestHydrationCoordinator::new n'est plus utilisé en production depuis la registry globale pre.008
|
||||||
|
```
|
||||||
|
|
||||||
|
Malgré l'échec Clippy, `cargo test -p ksp-worker-raw-transaction-ingest-lib` compile et passe ensuite : `94/94` tests unitaires et toutes les suites d'intégration affichées sont vertes.
|
||||||
|
|
||||||
|
## Objectif du fix
|
||||||
|
|
||||||
|
Corriger strictement ces quatre diagnostics sans élargir l'API interne et sans modifier le contrat de convergence :
|
||||||
|
|
||||||
|
```text
|
||||||
|
YellowstoneSource::run : pub(crate) -> privé au module
|
||||||
|
HeliusTransactionSource::run : pub(crate) -> privé au module
|
||||||
|
StandardLogsSource::run : pub(crate) -> privé au module
|
||||||
|
HydrationCoordinator::new : compilé uniquement sous #[cfg(test)]
|
||||||
|
GlobalHydrationRegistry : reste privée au module
|
||||||
|
```
|
||||||
|
|
||||||
|
Les trois méthodes `run` concernées ne sont appelées que par `RawTransactionIngestLiveSource::run` dans le même module. La registry globale ne doit donc pas être rendue `pub(crate)` uniquement pour satisfaire la visibilité d'une signature.
|
||||||
|
|
||||||
|
Le constructeur `RawTransactionIngestHydrationCoordinator::new` reste disponible pour les tests unitaires historiques mais n'entre plus dans le binaire production ; le runtime `pre.008` utilise `with_global_registry` avec la registry partagée du supervisor.
|
||||||
|
|
||||||
|
## Version
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison : 0.3.13-pre.008-fix.002
|
||||||
|
workspace.package.version : 0.3.13-pre.8.fix.2
|
||||||
|
archive : ksp-general-0.3.13-pre.008-fix.002.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
`Cargo.toml` passe de la version de fichier `554` à `555`.
|
||||||
|
|
||||||
|
## Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
|
||||||
|
```
|
||||||
|
|
||||||
|
`runtime_resources.rs` passe de la version de fichier `22` à `23`.
|
||||||
|
|
||||||
|
## Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.3.13/pre.008-fix.002.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers supprimés
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun
|
||||||
|
```
|
||||||
|
|
||||||
|
## Non-claims
|
||||||
|
|
||||||
|
Ce fix ne modifie aucune dépendance, aucune API publique, aucune clé de convergence, aucune borne de registry/cache, aucune sémantique d'hydration, aucune observation/provenance, aucun comportement Store et aucune politique multi-source.
|
||||||
|
|
||||||
|
## Validation dans l'environnement d'assemblage
|
||||||
|
|
||||||
|
Le toolchain Rust/Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Les commandes Cargo post-fix restent donc `NON EXÉCUTÉ LOCAL`. Les audits statiques KSP et le contrôle exhaustif du delta sont exécutés avant packaging.
|
||||||
|
|
||||||
|
## Gate opérateur requis avant pre.009
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
198
deltas/0.3.13/pre.008.md
Normal file
198
deltas/0.3.13/pre.008.md
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
<!-- file: deltas/0.3.13/pre.008.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta 0.3.13-pre.008 — convergence cross-source et observations multiples
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison précédente : 0.3.13-pre.007
|
||||||
|
Cargo base : 0.3.13-pre.7
|
||||||
|
delta base : deltas/0.3.13/pre.007.md
|
||||||
|
archive delta base : ksp-general-0.3.13-pre.007.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur communiqué le 10 septembre 2026 est vert pour toutes les commandes exécutées : `cargo fmt`, `cargo fmt --check`, audits Rust/Markdown, `cargo check --workspace`, Clippy strict et toutes les suites `ksp-worker-raw-transaction-ingest-lib` (`90` unit, `4` cross-layer, `14` dependency-boundary, `21` hardening, `15` public-api, `4` release-completeness, `0` doc-test). Le log reçu ne contient pas les sorties séparées `cargo test -p ksp-onchain-transport-lib` ni les trois commandes `cargo tree`; elles ne sont donc pas déclarées PASS ici.
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Fermer la convergence des sources live simultanées sans dupliquer l'identité `RawTransaction` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
plusieurs sources reference-bearing
|
||||||
|
-> une hydration globale par (network, signature, commitment)
|
||||||
|
-> acquisitions source-distinctes
|
||||||
|
-> canonicalisation RAW commune
|
||||||
|
-> convergence par (network, signature) + content hash
|
||||||
|
-> une entity RAW
|
||||||
|
-> plusieurs observations déterministes
|
||||||
|
```
|
||||||
|
|
||||||
|
## Version
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison : 0.3.13-pre.008
|
||||||
|
workspace.package.version : 0.3.13-pre.8
|
||||||
|
archive : ksp-general-0.3.13-pre.008.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
`Cargo.toml` passe de la version de fichier `552` à `553`.
|
||||||
|
|
||||||
|
## Hydration globale cross-source
|
||||||
|
|
||||||
|
`RawTransactionIngestRuntimeResources::run_live_sources` crée un `RawTransactionIngestGlobalHydrationRegistry` privé partagé par Yellowstone, Standard Logs et Helius Transaction. Standard Block et HTTP Block Polling restent RAW-direct et n'entrent pas dans ce registre.
|
||||||
|
|
||||||
|
La clé reste exactement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
(network, signature, commitment)
|
||||||
|
```
|
||||||
|
|
||||||
|
Le premier demandeur devient leader et exécute le `getTransaction observed` existant. Les suivants s'abonnent au même résultat via un `watch` privé. La registry est bornée par `admission_queue_capacity`, publie soit le résultat observed soit un `ErrorCode` sûr, puis retire la clé.
|
||||||
|
|
||||||
|
Les signaux source restent distincts : le même résultat d'hydration est finalisé séparément avec le contexte/provenance de chaque signal.
|
||||||
|
|
||||||
|
## Convergence persistence
|
||||||
|
|
||||||
|
Le runtime possède un `RawTransactionIngestPersistenceConvergence` privé et run-local. Sa capacité productive est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
max(admission_queue_capacity, persistence_concurrency)
|
||||||
|
```
|
||||||
|
|
||||||
|
Chaque identité `(network, signature)` possède un verrou async local et mémorise le `RawContentHash` canonique après une première persistence réussie non purgée.
|
||||||
|
|
||||||
|
```text
|
||||||
|
première acquisition
|
||||||
|
-> persist_raw_transaction_acquisition
|
||||||
|
-> Store atomique entity + observation
|
||||||
|
|
||||||
|
même identity + même content hash
|
||||||
|
-> record_raw_transaction_observation uniquement
|
||||||
|
|
||||||
|
même identity + hash divergent
|
||||||
|
-> content conflict terminal
|
||||||
|
```
|
||||||
|
|
||||||
|
Le cache évince uniquement une entrée inactive lorsqu'il atteint sa borne. Le runtime dimensionne cette borne pour couvrir toutes les tâches persistence simultanées valides.
|
||||||
|
|
||||||
|
## Observations multiples
|
||||||
|
|
||||||
|
L'`observation_key` reste dérivée de la référence canonique et du `source_key` privé. Deux sources distinctes observant le même RAW produisent donc une seule référence/entity mais des observations déterministes distinctes.
|
||||||
|
|
||||||
|
Un replay de la même observation retourne `AlreadyPresent` sans réécriture de l'entité. Une observation supplémentaire `NotRecorded` est incohérente pour une entité déjà durable et devient une faute runtime sûre.
|
||||||
|
|
||||||
|
## Preuves ajoutées
|
||||||
|
|
||||||
|
Tests unitaires déterministes :
|
||||||
|
|
||||||
|
```text
|
||||||
|
deux source_key -> même référence/hash canonique, observation_key distinctes
|
||||||
|
registry globale -> un leader, un follower, publication partagée puis retrait
|
||||||
|
première identity -> une persistence atomique
|
||||||
|
seconde observation distincte -> record_observation seul
|
||||||
|
replay même observation -> AlreadyPresent sans nouvelle persistence entity
|
||||||
|
```
|
||||||
|
|
||||||
|
Canaris externes :
|
||||||
|
|
||||||
|
```text
|
||||||
|
dependency_boundary : Store/Transport facades uniquement, pas de backend/client direct
|
||||||
|
hardening : caches bornés, hash conflict-checked, convergence privée, aucun canal unbounded
|
||||||
|
public_api : registry/cache/hydration key/source_key non exposés
|
||||||
|
release_completeness : présence obligatoire des trois canaris pre.008
|
||||||
|
```
|
||||||
|
|
||||||
|
## Frontière de tranche
|
||||||
|
|
||||||
|
`pre.008` ne ferme pas encore :
|
||||||
|
|
||||||
|
```text
|
||||||
|
fairness adversariale sous duplicate storm prolongé
|
||||||
|
starvation entre sources
|
||||||
|
matrice complète de disagreement provider/source
|
||||||
|
stress des bornes globales
|
||||||
|
nouveaux compteurs/health publics multi-source
|
||||||
|
```
|
||||||
|
|
||||||
|
Ces points restent `pre.009` et `pre.010` selon le plan.
|
||||||
|
|
||||||
|
## Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/README.md
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/USAGE.md
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/src/lib.rs
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/src/persistence.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/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
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/admission.rs
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/persistence.rs
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime.rs
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
|
||||||
|
docs/plans/034-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE_PLAN.md
|
||||||
|
docs/validation/030-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.3.13/pre.008.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers supprimés
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation dans l'environnement d'assemblage
|
||||||
|
|
||||||
|
Le toolchain Rust/Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Les commandes Cargo post-`pre.008` restent donc `NON EXÉCUTÉ LOCAL`. Les audits statiques KSP, le scan normatif, le contrôle exhaustif du diff, des headers et du ZIP sont exécutés avant livraison.
|
||||||
|
|
||||||
|
Résultats statiques finaux :
|
||||||
|
|
||||||
|
```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, 842 files)
|
||||||
|
Normative rule definitions: 489
|
||||||
|
Unique normative IDs: 489
|
||||||
|
Duplicates: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
Contrôle ciblé :
|
||||||
|
|
||||||
|
```text
|
||||||
|
17 fichiers existants modifiés
|
||||||
|
1 fichier ajouté
|
||||||
|
0 suppression
|
||||||
|
17/17 headers existants : +1
|
||||||
|
nouvelle dépendance : aucune
|
||||||
|
backend Store physique direct : absent
|
||||||
|
client HTTP/gRPC direct : absent
|
||||||
|
unbounded_channel : absent
|
||||||
|
convergence/registry publiques : absentes
|
||||||
|
```
|
||||||
|
|
||||||
|
## Gate opérateur requis avant pre.009
|
||||||
|
|
||||||
|
```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
|
||||||
|
cargo test -p ksp-worker-raw-transaction-ingest-lib
|
||||||
|
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
|
||||||
|
```
|
||||||
137
deltas/0.3.13/pre.009-fix.001.md
Normal file
137
deltas/0.3.13/pre.009-fix.001.md
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
<!-- file: deltas/0.3.13/pre.009-fix.001.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta 0.3.13-pre.009-fix.001 — alignement des canaris de quotas et correction du gate cargo tree
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison précédente : 0.3.13-pre.009
|
||||||
|
Cargo base : 0.3.13-pre.9
|
||||||
|
delta base : deltas/0.3.13/pre.009.md
|
||||||
|
archive delta base : ksp-general-0.3.13-pre.009.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur du 10 septembre 2026 confirme :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt + --check : PASS
|
||||||
|
audits Rust : clean / export completeness 0
|
||||||
|
Markdown : clean, 340 tables / 845 files
|
||||||
|
cargo check --workspace : PASS
|
||||||
|
Clippy strict workspace/all-targets/all-features : PASS
|
||||||
|
Transport unit : 389/389 PASS
|
||||||
|
Transport public API : 52/52 PASS
|
||||||
|
Transport release completeness : 44/44 PASS
|
||||||
|
Transport doc-tests : 4/4 PASS
|
||||||
|
Transport smokes live opt-in : 5 ignored comme prévu
|
||||||
|
Worker unit : 99/99 PASS
|
||||||
|
Worker cross-layer : 4/4 PASS
|
||||||
|
Worker dependency-boundary : 13 PASS / 3 FAIL
|
||||||
|
```
|
||||||
|
|
||||||
|
La suite Worker s'arrête sur ces trois canaris `dependency_boundary`; `hardening`, `public_api`, `release_completeness` et les doc-tests Worker ne sont donc pas déclarés PASS pour ce gate.
|
||||||
|
|
||||||
|
Les trois échecs exigent encore la chaîne historique :
|
||||||
|
|
||||||
|
```text
|
||||||
|
max_pending_signals: settings.admission_queue_capacity()
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette assertion n'est plus correcte depuis `pre.009`. Les sources reference-bearing reçoivent désormais des quotas `hydration_pending_limit` et `hydration_in_flight_limit` calculés à partir des budgets globaux, tandis que les preuves `pre.009` verrouillent la partition exacte des capacités configurées.
|
||||||
|
|
||||||
|
Une revue préventive trouve également le même canari obsolète dans `tests/hardening.rs`, avec l'ancienne hypothèse `max_in_flight: settings.persistence_concurrency()`. Il est corrigé dans le même fix avant qu'il ne devienne le prochain échec.
|
||||||
|
|
||||||
|
## Objectif du fix
|
||||||
|
|
||||||
|
Mettre à jour les preuves sans modifier la logique runtime :
|
||||||
|
|
||||||
|
```text
|
||||||
|
anciens canaris source-specific : vérifient with_global_registry + quotas source locaux
|
||||||
|
canari pre.009 dependency : vérifie aussi le lien budgets globaux -> settings
|
||||||
|
hardening historique : vérifie les champs bornés + quotas, sans ancienne égalité locale
|
||||||
|
runtime : inchangé
|
||||||
|
```
|
||||||
|
|
||||||
|
Les anciens canaris ne sont pas affaiblis : la relation directe entre capacités Worker et budgets globaux est désormais vérifiée par le canari `pre.009`, et les canaris historiques continuent à vérifier que chaque source utilise le coordinator borné commun.
|
||||||
|
|
||||||
|
## Correction du workflow cargo tree
|
||||||
|
|
||||||
|
Le gate opérateur avant `pre.010` ne doit pas imposer `cargo tree` : `pre.009` ne modifie aucune dépendance ni feature et `pre.010` n'est pas la fermeture technique de `0.3.13`.
|
||||||
|
|
||||||
|
La règle courante est explicitée dans le prompt `0.3.13` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo tree requis si le graphe dépendances/features change
|
||||||
|
ou à la fermeture technique de 0.3.13
|
||||||
|
pas à chaque prerelease sans changement de graphe
|
||||||
|
```
|
||||||
|
|
||||||
|
Le plan, la validation et le delta `pre.009` sont alignés sur cette règle pour le gate avant `pre.010`.
|
||||||
|
|
||||||
|
## Version
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison : 0.3.13-pre.009-fix.001
|
||||||
|
workspace.package.version : 0.3.13-pre.9.fix.1
|
||||||
|
archive : ksp-general-0.3.13-pre.009-fix.001.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
`Cargo.toml` passe de la version de fichier `556` à `557`.
|
||||||
|
|
||||||
|
## Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/tests/dependency_boundary.rs
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
|
||||||
|
prompts/032-V0_3_13_START_PROMPT.md
|
||||||
|
docs/plans/034-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE_PLAN.md
|
||||||
|
docs/validation/030-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE.md
|
||||||
|
deltas/0.3.13/pre.009.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Versions de fichiers :
|
||||||
|
|
||||||
|
```text
|
||||||
|
dependency_boundary.rs : 25 -> 26
|
||||||
|
hardening.rs : 19 -> 20
|
||||||
|
032 start prompt : 1 -> 2
|
||||||
|
plan 034 : 9 -> 10
|
||||||
|
validation 030 : 9 -> 10
|
||||||
|
pre.009.md : 1 -> 2
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.3.13/pre.009-fix.001.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers supprimés
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun
|
||||||
|
```
|
||||||
|
|
||||||
|
## Non-claims
|
||||||
|
|
||||||
|
Ce fix ne modifie aucune source production, aucun quota calculé, aucune borne globale, aucun sémaphore, aucune registry, aucune logique de disagreement, aucune dépendance, aucune feature et aucune API publique.
|
||||||
|
|
||||||
|
## Validation dans l'environnement d'assemblage
|
||||||
|
|
||||||
|
Le toolchain Rust/Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Les commandes Cargo post-fix restent donc `NON EXÉCUTÉ LOCAL`. Les audits statiques KSP et le contrôle exhaustif du delta sont exécutés avant packaging.
|
||||||
|
|
||||||
|
## Gate opérateur requis avant pre.010
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun `cargo tree` n'est requis pour ce gate. Le test Transport n'a pas besoin d'être répété par ce fix, qui ne modifie ni Transport, ni dépendance, ni feature.
|
||||||
148
deltas/0.3.13/pre.009-fix.002.md
Normal file
148
deltas/0.3.13/pre.009-fix.002.md
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
<!-- file: deltas/0.3.13/pre.009-fix.002.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta 0.3.13-pre.009-fix.002 — alignement du canari de disagreement canonique
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison précédente : 0.3.13-pre.009-fix.001
|
||||||
|
Cargo base : 0.3.13-pre.9.fix.1
|
||||||
|
delta base : deltas/0.3.13/pre.009-fix.001.md
|
||||||
|
archive delta base : ksp-general-0.3.13-pre.009-fix.001.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur du 10 septembre 2026 confirme :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt + --check : PASS
|
||||||
|
audits Rust : clean / export completeness 0
|
||||||
|
Markdown : clean, 340 tables / 846 files
|
||||||
|
cargo check --workspace : PASS
|
||||||
|
Clippy strict workspace/all-targets/all-features : PASS
|
||||||
|
Transport unit : 389/389 PASS
|
||||||
|
Transport public API : 52/52 PASS
|
||||||
|
Transport release completeness : 44/44 PASS
|
||||||
|
Transport doc-tests : 4/4 PASS
|
||||||
|
Transport smokes live opt-in : 5 ignored comme prévu
|
||||||
|
Worker unit : 99/99 PASS
|
||||||
|
Worker cross-layer : 4/4 PASS
|
||||||
|
Worker dependency-boundary : 16/16 PASS
|
||||||
|
Worker hardening : 22 PASS / 1 FAIL
|
||||||
|
```
|
||||||
|
|
||||||
|
La suite Worker s'arrête sur le canari :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0_3_13_pre_008_cross_source_convergence_is_bounded_conflict_checked_and_private
|
||||||
|
```
|
||||||
|
|
||||||
|
Il exige encore l'ancienne chaîne d'implémentation :
|
||||||
|
|
||||||
|
```text
|
||||||
|
known_hash_value != content_hash
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette assertion est obsolète depuis `pre.009`, où l'état canonique de convergence compare désormais ensemble `slot`, `block_time`, `format_id`, `format_version` et `content_hash` via `RawTransactionIngestCanonicalState`.
|
||||||
|
|
||||||
|
`public_api`, `release_completeness` et les doc-tests Worker ne sont donc pas déclarés PASS pour ce gate, car l'exécution s'arrête sur `hardening`.
|
||||||
|
|
||||||
|
## Objectif du fix
|
||||||
|
|
||||||
|
Corriger uniquement le canari historique `pre.008` afin qu'il vérifie le conflit canonique actuel sans revenir à une comparaison hash-only :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ancienne preuve : known_hash_value != content_hash
|
||||||
|
nouvelle preuve : known_state_value != &canonical_state
|
||||||
|
```
|
||||||
|
|
||||||
|
Le canari continue à exiger la borne du cache, l'éviction uniquement des entrées non partagées, le passage par `record_observation` et le fail-closed d'une observation additionnelle impossible.
|
||||||
|
|
||||||
|
Aucune source production n'est modifiée.
|
||||||
|
|
||||||
|
## Immutabilité des deltas
|
||||||
|
|
||||||
|
Les deltas déjà livrés sont immuables. Ce fix n'altère aucun fichier `deltas/` existant, y compris :
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.3.13/pre.009.md
|
||||||
|
deltas/0.3.13/pre.009-fix.001.md
|
||||||
|
```
|
||||||
|
|
||||||
|
La correction est tracée uniquement par le nouveau fichier `deltas/0.3.13/pre.009-fix.002.md`.
|
||||||
|
|
||||||
|
La modification de `pre.009.md` qui avait été incluse dans `pre.009-fix.001` reste une exception historique déjà acceptée par l'opérateur ; elle n'est ni répétée ni réécrite dans ce fix.
|
||||||
|
|
||||||
|
## Version
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison : 0.3.13-pre.009-fix.002
|
||||||
|
workspace.package.version : 0.3.13-pre.9.fix.2
|
||||||
|
archive : ksp-general-0.3.13-pre.009-fix.002.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
`Cargo.toml` passe de la version de fichier `557` à `558`.
|
||||||
|
|
||||||
|
## Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/tests/hardening.rs
|
||||||
|
```
|
||||||
|
|
||||||
|
`hardening.rs` passe de la version de fichier `20` à `21`.
|
||||||
|
|
||||||
|
## Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.3.13/pre.009-fix.002.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers supprimés
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validations exécutées dans l'environnement d'assemblage
|
||||||
|
|
||||||
|
Le toolchain Rust/Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Les commandes Cargo post-fix restent donc `NON EXÉCUTÉ LOCAL`.
|
||||||
|
|
||||||
|
Sont exécutés avant packaging :
|
||||||
|
|
||||||
|
```text
|
||||||
|
scripts/audit_rust_workspace_rules.py
|
||||||
|
audit Markdown complet
|
||||||
|
scan des définitions normatives
|
||||||
|
diff exact contre la base pre.009-fix.001
|
||||||
|
contrôle d'immutabilité byte-for-byte de tous les deltas existants
|
||||||
|
contrôle du manifest et du contenu extrait de l'archive
|
||||||
|
```
|
||||||
|
|
||||||
|
## Décisions
|
||||||
|
|
||||||
|
- le runtime `pre.009` reste inchangé ;
|
||||||
|
- le disagreement canonique reste fondé sur l'état canonique complet, pas sur le seul hash ;
|
||||||
|
- aucun delta historique n'est modifié ;
|
||||||
|
- aucune dépendance ni feature ne change ;
|
||||||
|
- aucun `cargo tree` n'est requis pour ce fix.
|
||||||
|
|
||||||
|
## Questions ouvertes
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucune
|
||||||
|
```
|
||||||
|
|
||||||
|
## Gate opérateur requis avant pre.010
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
Le test Transport n'a pas besoin d'être répété pour ce fix, qui ne modifie ni Transport, ni source production, ni dépendance, ni feature.
|
||||||
221
deltas/0.3.13/pre.009.md
Normal file
221
deltas/0.3.13/pre.009.md
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
<!-- file: deltas/0.3.13/pre.009.md -->
|
||||||
|
<!-- version: 2 -->
|
||||||
|
|
||||||
|
# Delta 0.3.13-pre.009 — disagreements, duplicate storms, bornes globales et fairness
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison précédente : 0.3.13-pre.008-fix.002
|
||||||
|
Cargo base : 0.3.13-pre.8.fix.2
|
||||||
|
delta base : deltas/0.3.13/pre.008-fix.002.md
|
||||||
|
archive delta base : ksp-general-0.3.13-pre.008-fix.002.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
Les gates opérateur communiqués le 10 septembre 2026 autorisent cette tranche. Le gate Worker est vert pour `cargo fmt`, `cargo fmt --check`, les audits Rust/Markdown, `cargo check --workspace`, Clippy strict et toutes les suites `ksp-worker-raw-transaction-ingest-lib` : `94` unit, `4` cross-layer, `15` dependency-boundary, `22` hardening, `16` public-api, `4` release-completeness et `0` doc-test.
|
||||||
|
|
||||||
|
Le gate étendu complète cette qualification avec `cargo test -p ksp-onchain-transport-lib` : `389` unit, `52` public-api, `44` release-completeness et `4` doc-tests passent. Les cinq smokes réseau opt-in affichés restent ignorés comme prévu. Les commandes `cargo tree -p ksp-worker-raw-transaction-ingest-lib --edges normal`, `cargo tree -p ksp-worker-raw-transaction-ingest-lib -e features` et `cargo tree --duplicates` sont exécutées sans erreur ; `cargo tree --duplicates` liste les doublons existants du workspace sans introduire de promesse de graphe sans doublon.
|
||||||
|
|
||||||
|
## Objectif
|
||||||
|
|
||||||
|
Fermer les risques adversariaux qui restent après la convergence cross-source de `pre.008` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
disagreement entre sources
|
||||||
|
storms de références dupliquées
|
||||||
|
borne globale des pending hydration signals
|
||||||
|
borne globale des tâches/hydrations HTTP
|
||||||
|
cleanup d'un leader global annulé
|
||||||
|
fairness minimale entre sources reference-bearing
|
||||||
|
absence de starvation structurelle
|
||||||
|
```
|
||||||
|
|
||||||
|
La tranche ne modifie pas encore la projection publique de health/snapshots multi-source ; ce point reste réservé à `pre.010`.
|
||||||
|
|
||||||
|
## Version
|
||||||
|
|
||||||
|
```text
|
||||||
|
livraison : 0.3.13-pre.009
|
||||||
|
workspace.package.version : 0.3.13-pre.9
|
||||||
|
archive : ksp-general-0.3.13-pre.009.zip
|
||||||
|
```
|
||||||
|
|
||||||
|
`Cargo.toml` passe de la version de fichier `555` à `556`.
|
||||||
|
|
||||||
|
## Budgets globaux et fairness minimale
|
||||||
|
|
||||||
|
Yellowstone, Standard Logs et Helius Transaction sont les sources reference-bearing qui nécessitent une hydration `getTransaction`. Avant tout spawn, `run_live_sources` vérifie :
|
||||||
|
|
||||||
|
```text
|
||||||
|
hydration_source_count <= admission_queue_capacity
|
||||||
|
hydration_source_count <= persistence_concurrency
|
||||||
|
```
|
||||||
|
|
||||||
|
Si une de ces relations est fausse, le démarrage échoue fail-closed avec un `runtime_invalid` stable. Aucune capacité configurée n'est agrandie silencieusement.
|
||||||
|
|
||||||
|
Lorsque la composition est valide, les deux budgets sont partitionnés statiquement par quotient/reste suivant l'ordre déjà validé des sources :
|
||||||
|
|
||||||
|
```text
|
||||||
|
somme hydration pending quotas = admission_queue_capacity
|
||||||
|
somme hydration in-flight quotas = persistence_concurrency
|
||||||
|
quota de chaque source reference-bearing >= 1
|
||||||
|
```
|
||||||
|
|
||||||
|
Une source ne peut donc pas consommer la part réservée aux autres sources reference-bearing. Ce mécanisme n'introduit ni scheduler pondéré, ni priorité provider, ni primary/standby.
|
||||||
|
|
||||||
|
Standard Block et HTTP Block Polling restent RAW-direct et continuent à utiliser l'admission centrale bornée sans passer par ces quotas d'hydration.
|
||||||
|
|
||||||
|
## Borne globale d'hydration
|
||||||
|
|
||||||
|
Le `RawTransactionIngestGlobalHydrationRegistry` reste privé et borné à `admission_queue_capacity` clés distinctes. Un duplicate storm sur une clé déjà présente rejoint le résultat global existant et ne crée ni nouvelle entrée ni second leader HTTP.
|
||||||
|
|
||||||
|
Un `tokio::sync::Semaphore` global, dimensionné à `persistence_concurrency`, protège en plus le nombre de leaders HTTP effectivement ouverts. Les coordinators locaux conservent simultanément leurs quotas `max_pending_signals` et `max_in_flight`.
|
||||||
|
|
||||||
|
Une clé distincte au-delà de la borne globale produit `source.global_hydration_pending_saturated` au lieu d'une croissance mémoire non bornée.
|
||||||
|
|
||||||
|
## Cleanup d'un leader global
|
||||||
|
|
||||||
|
Chaque leader de la registry possède un `RawTransactionIngestHydrationLeaderGuard` privé. Si la tâche est annulée ou droppée avant la publication normale du résultat, le guard publie une faute source sûre aux followers et retire la clé du registre.
|
||||||
|
|
||||||
|
Après cleanup, la même clé peut redevenir leader lors d'une acquisition ultérieure. Aucun leader orphelin ne doit survivre à un abort/shutdown.
|
||||||
|
|
||||||
|
## Disagreement canonique
|
||||||
|
|
||||||
|
Le cache run-local de convergence persistence ne compare plus uniquement `RawContentHash`. Pour une même identité durable `(network, signature)`, il mémorise maintenant un état canonique borné :
|
||||||
|
|
||||||
|
```text
|
||||||
|
slot
|
||||||
|
block_time
|
||||||
|
format_id
|
||||||
|
format_version
|
||||||
|
content_hash
|
||||||
|
```
|
||||||
|
|
||||||
|
Une divergence sur l'une de ces dimensions produit immédiatement le content conflict terminal existant avant toute observation additionnelle. Aucun byte de transaction n'est recopié dans le cache ; le hash provient de la canonicalisation Common RAW et le Store conserve son contrôle durable final sur le contenu exact.
|
||||||
|
|
||||||
|
La politique reste conservatrice : aucune majorité de sources, aucun first-provider-wins, aucune préférence provider/tier et aucun overwrite de conflit.
|
||||||
|
|
||||||
|
## Fairness et duplicate storms
|
||||||
|
|
||||||
|
La fairness minimale est prouvée sur le canal d'admission commun borné à capacité `1`. Après qu'une source en storm a rejoint la file d'attente, une seconde source déjà prête rejoint la même file et progresse avant la répétition suivante de la storm.
|
||||||
|
|
||||||
|
Cette preuve ne revendique pas une QoS pondérée. Elle verrouille uniquement l'absence de starvation indéfinie d'une source déjà prête sous le contrat FIFO du canal borné commun, tandis que les quotas statiques empêchent la starvation structurelle des sources reference-bearing en amont.
|
||||||
|
|
||||||
|
## Preuves ajoutées
|
||||||
|
|
||||||
|
Tests unitaires déterministes :
|
||||||
|
|
||||||
|
```text
|
||||||
|
budgets 8/5 répartis sur 3 sources -> [3,3,2] pending et [2,2,1] in-flight
|
||||||
|
composition sous-provisionnée -> refus avant spawn
|
||||||
|
32 duplicats d'une même clé -> un seul leader global
|
||||||
|
clé distincte au-delà de max_pending -> saturation sûre
|
||||||
|
sémaphore capacité 1 -> une seule permit simultanée
|
||||||
|
leader guard droppé -> Failed publié + clé réouvrable
|
||||||
|
queue admission capacité 1 -> seconde source prête progresse sous storm
|
||||||
|
même signature + slot divergent -> content conflict
|
||||||
|
même signature + block_time divergent -> content conflict
|
||||||
|
même signature + contenu/hash divergent -> content conflict
|
||||||
|
aucune observation additionnelle après disagreement
|
||||||
|
```
|
||||||
|
|
||||||
|
Canaris externes :
|
||||||
|
|
||||||
|
```text
|
||||||
|
dependency_boundary : bornes/fairness restent dans Worker + façades existantes
|
||||||
|
hardening : quotas, sémaphore, cleanup, disagreement et absence de provider preference
|
||||||
|
public_api : registry, leader guard, quotas et canonical state restent privés
|
||||||
|
release_completeness : présence obligatoire des trois canaris pre.009
|
||||||
|
```
|
||||||
|
|
||||||
|
## Frontière de tranche
|
||||||
|
|
||||||
|
`pre.009` ne ferme pas encore :
|
||||||
|
|
||||||
|
```text
|
||||||
|
nouveaux compteurs/health publics multi-source
|
||||||
|
projection détaillée par source
|
||||||
|
politique de dégradation non terminale
|
||||||
|
failover provider
|
||||||
|
scheduler/QoS pondéré
|
||||||
|
source primary/standby
|
||||||
|
majority vote
|
||||||
|
```
|
||||||
|
|
||||||
|
Les snapshots/health appartiennent à `pre.010`.
|
||||||
|
|
||||||
|
## Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/README.md
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/USAGE.md
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/src/persistence.rs
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/src/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/public_api.rs
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/tests/release_completeness.rs
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/admission.rs
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/persistence.rs
|
||||||
|
crates/ksp-worker-raw-transaction-ingest-lib/unit_tests/runtime_resources.rs
|
||||||
|
docs/plans/034-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE_PLAN.md
|
||||||
|
docs/validation/030-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.3.13/pre.009.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fichiers supprimés
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation dans l'environnement d'assemblage
|
||||||
|
|
||||||
|
Le toolchain Rust/Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Les commandes Cargo post-`pre.009` restent donc `NON EXÉCUTÉ LOCAL`. Les audits statiques KSP, le scan normatif, le contrôle exhaustif du diff, des headers et du ZIP sont exécutés avant livraison.
|
||||||
|
|
||||||
|
## Résultats statiques finaux
|
||||||
|
|
||||||
|
```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, 845 files)
|
||||||
|
Normative rule definitions: 489
|
||||||
|
Unique normative IDs: 489
|
||||||
|
Duplicates: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
Contrôle ciblé :
|
||||||
|
|
||||||
|
```text
|
||||||
|
14 fichiers existants modifiés
|
||||||
|
1 fichier ajouté
|
||||||
|
0 suppression
|
||||||
|
14/14 headers existants : +1
|
||||||
|
nouvelle dépendance : aucune
|
||||||
|
backend Store physique direct : absent
|
||||||
|
client HTTP/gRPC direct : absent
|
||||||
|
unbounded_channel : absent
|
||||||
|
registry/quotas/canonical state publics : absents
|
||||||
|
```
|
||||||
|
|
||||||
|
## Gate opérateur requis avant pre.010
|
||||||
|
|
||||||
|
```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
|
||||||
|
cargo test -p ksp-worker-raw-transaction-ingest-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun `cargo tree` n'est requis pour ce gate : `pre.009` ne modifie ni dépendance ni feature. Les graphes seront réaudités si le graphe change ou à la fermeture technique de `0.3.13`.
|
||||||
48
deltas/0.3.13/pre.010.md
Normal file
48
deltas/0.3.13/pre.010.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<!-- file: deltas/0.3.13/pre.010.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta 0.3.13-pre.010 — snapshots/health multi-source
|
||||||
|
|
||||||
|
## Objet
|
||||||
|
|
||||||
|
Fermer la projection publique source-neutral prévue pour le runtime multi-source sans exposer les identités logiques, providers, endpoints, filtres ou matériaux Transport.
|
||||||
|
|
||||||
|
## Runtime
|
||||||
|
|
||||||
|
`RawTransactionIngestSnapshot` expose désormais quatre gauges latest-value : `source_total`, `source_active`, `source_reconnecting` et `source_failed`.
|
||||||
|
|
||||||
|
`source_total` est initialisé dès le démarrage depuis la collection `RawTransactionIngestRuntimeResources` validée. L'inventaire privé borné agrège ensuite les états locaux et publie uniquement les comptes numériques, l'état source agrégé et les compteurs de continuité déjà existants.
|
||||||
|
|
||||||
|
La health commune devient conservative pendant `Running` : une source failed donne `Unhealthy`; un reconnect ou une composition dont toutes les sources attendues ne sont pas encore actives donne `Degraded`; toutes les sources attendues actives permettent `Healthy`. `Faulted` reste `Unhealthy` et `Stopping`/`Stopped` conservent la dernière health qualifiée.
|
||||||
|
|
||||||
|
L'activity reste liée au travail concret, aux pending, à la persistence, au reconnect ou au closing. Une source simplement ouverte n'est pas artificiellement classée `Active`.
|
||||||
|
|
||||||
|
## Preuves ajoutées
|
||||||
|
|
||||||
|
Les tests couvrent les comptes d'inventaire, la health `Degraded -> Healthy -> Unhealthy`, les quatre getters publics, la redaction du snapshot, l'absence de backend/network client direct et la présence des canaris dans `release_completeness`.
|
||||||
|
|
||||||
|
## Frontière
|
||||||
|
|
||||||
|
Aucun changement de convergence, fairness, quota, Store, Transport, dépendance ou feature. Aucun failover, provider health public, snapshot par source ou coverage inference. Les races et shutdown concurrents restent `pre.011`.
|
||||||
|
|
||||||
|
## Gate reçu avant cette tranche
|
||||||
|
|
||||||
|
Le gate opérateur `0.3.13-pre.009-fix.002` est vert pour toutes les commandes exécutées : fmt, audits, workspace check, Clippy strict, Transport `389 + 52 + 44 + 4` avec `5` smokes live opt-in ignorés, puis Worker `99 + 4 + 16 + 23 + 17 + 4`, sans échec. Aucun `cargo tree` n'était requis ni exécuté.
|
||||||
|
|
||||||
|
## Validation locale d'assemblage
|
||||||
|
|
||||||
|
Le toolchain Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Aucun résultat Cargo post-modification n'est déclaré PASS localement. Les audits statiques et contrôles de packaging effectivement exécutés sont les seules preuves locales consignées pour cette livraison.
|
||||||
|
|
||||||
|
## Gate opérateur avant pre.011
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun `cargo tree` n'est requis ; aucune dépendance ni feature n'a changé. Le Transport n'est pas modifié par cette tranche snapshot/health.
|
||||||
62
deltas/0.3.13/pre.011.md
Normal file
62
deltas/0.3.13/pre.011.md
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<!-- file: deltas/0.3.13/pre.011.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta 0.3.13-pre.011 — races/shutdown hardening
|
||||||
|
|
||||||
|
## Objet
|
||||||
|
|
||||||
|
Fermer les races stop/fault et l'ownership des tâches source/hydration/persistence avant la tranche de completeness/security cross-layer, sans modifier Transport, le graphe de dépendances ou les politiques de convergence/fairness déjà acquises.
|
||||||
|
|
||||||
|
## Shutdown et ownership
|
||||||
|
|
||||||
|
Le supervisor conserve l'ordre terminal explicite : signal de stop source, passage Worker à `Stopping`, drain borné, abort+join si la deadline expire, puis publication du terminal. L'abort du wrapper multi-source détruit son `JoinSet` privé et annule donc aussi les tâches source imbriquées avant retour.
|
||||||
|
|
||||||
|
Une faute source déjà observée n'est pas masquée par un stop concurrent. Le `drain_timeout` reste prioritaire lorsqu'une récupération bornée dépasse réellement sa deadline. Une persistence encore bloquée à cette échéance est abortée et jointe ; elle ne peut pas publier de succès tardif après le snapshot terminal.
|
||||||
|
|
||||||
|
## Hydration partagée
|
||||||
|
|
||||||
|
`RawTransactionIngestGlobalHydrationRegistry::publish_and_remove` notifie désormais les followers sous le mutex de registry avant de retirer la clé. Une nouvelle génération de leader ne peut donc pas s'intercaler entre le retrait de la clé et la notification des followers existants.
|
||||||
|
|
||||||
|
Le leader guard conserve son cleanup fail-closed : si un leader est aborté avant publication normale, les followers reçoivent une faute source sûre et la clé redevient ensuite disponible.
|
||||||
|
|
||||||
|
## Inventaire et compteurs
|
||||||
|
|
||||||
|
L'agrégation multi-source remplace les additions saturantes par `checked_add` pour `hydration_pending`, `source_reconnect_total`, `source_replay_attempt_total` et `source_continuity_gap_total`. Un overflow devient `worker_raw_transaction_ingest.counter_exhausted`.
|
||||||
|
|
||||||
|
Une mise à jour d'inventaire avec index absent, `source_key` incohérente ou projection absente échoue explicitement par `runtime_invalid` au lieu d'être ignorée. Lorsqu'une source remonte `counter_exhausted`, le supervisor préserve ce code terminal et ne le remappe pas en `source_failed`.
|
||||||
|
|
||||||
|
## Preuves ajoutées
|
||||||
|
|
||||||
|
Les tests couvrent l'identité/index d'inventaire invalides, les overflows d'agrégats, l'abort d'un leader hydration, l'abort du supervisor externe et de ses enfants imbriqués, la race stop/source fault, la conservation de `counter_exhausted`, le drain timeout sans completion tardive, l'ordre terminal et l'atomicité notification-avant-retrait.
|
||||||
|
|
||||||
|
Les canaris externes verrouillent l'absence de queue non bornée, backend Store direct, client Transport secondaire ou nouvelle surface publique d'identité source.
|
||||||
|
|
||||||
|
## Frontière
|
||||||
|
|
||||||
|
Aucun changement de Transport, Store API, dépendance, feature, convergence, quotas/fairness, snapshot public ou politique de failover. La completeness/security exhaustive reste `pre.012`; le gate technique/live complet et les graphes restent `pre.013`.
|
||||||
|
|
||||||
|
## Gate reçu avant cette tranche
|
||||||
|
|
||||||
|
Le gate opérateur `0.3.13-pre.010` est vert pour toutes les commandes exécutées : fmt, audits, Markdown `340/848`, workspace check, Clippy strict, puis Worker `100` unit + `4` cross-layer + `17` dependency-boundary + `24` hardening + `18` public-api + `4` release-completeness et `0` doc-test.
|
||||||
|
|
||||||
|
Le Transport n'était pas modifié par `pre.010` et n'a pas été rejoué. Aucun `cargo tree` n'était requis ni exécuté.
|
||||||
|
|
||||||
|
## Validation locale d'assemblage
|
||||||
|
|
||||||
|
Le toolchain Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Aucun résultat Cargo post-modification n'est déclaré PASS localement. Les audits statiques et contrôles de packaging effectivement exécutés sont les seules preuves locales consignées pour cette livraison.
|
||||||
|
|
||||||
|
Les audits statiques finaux d'assemblage sont clean : règles Rust générales, export completeness `0`, règles KSP, Markdown `340/849`, et inventaire normatif `489/489` sans doublon.
|
||||||
|
|
||||||
|
## Gate opérateur avant pre.012
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun `cargo tree` n'est requis ; aucune dépendance ni feature n'a changé. Le Transport n'est pas modifié par cette tranche de hardening shutdown/races.
|
||||||
75
deltas/0.3.13/pre.012.md
Normal file
75
deltas/0.3.13/pre.012.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
<!-- file: deltas/0.3.13/pre.012.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta 0.3.13-pre.012 — completeness/security cross-layer
|
||||||
|
|
||||||
|
## Objet
|
||||||
|
|
||||||
|
Fermer la matrice de preuves cross-layer avant le gate technique/live `pre.013`, sans modifier le comportement runtime, l'API publique, Transport, Common RAW, Store, les dépendances ou les features.
|
||||||
|
|
||||||
|
## Gate opérateur d'entrée
|
||||||
|
|
||||||
|
Le gate `0.3.13-pre.011` communiqué le 10 septembre 2026 est vert :
|
||||||
|
|
||||||
|
```text
|
||||||
|
fmt : PASS
|
||||||
|
audits Rust : clean / export completeness 0
|
||||||
|
Markdown : clean, 340 tables / 849 files
|
||||||
|
cargo check --workspace : PASS
|
||||||
|
clippy strict : PASS
|
||||||
|
Worker unit : 106/106 PASS
|
||||||
|
cross_layer_completeness : 4/4 PASS
|
||||||
|
dependency_boundary : 18/18 PASS
|
||||||
|
hardening : 25/25 PASS
|
||||||
|
public_api : 19/19 PASS
|
||||||
|
release_completeness : 4/4 PASS
|
||||||
|
Worker doc-tests : 0/0 PASS
|
||||||
|
```
|
||||||
|
|
||||||
|
## Modifications
|
||||||
|
|
||||||
|
La suite `cross_layer_completeness` ajoute quatre preuves obligatoires :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cinq familles live -> pipeline Worker/Common RAW/Store unique
|
||||||
|
Legacy/V0/V1 -> Transport/Worker/Common RAW/Store
|
||||||
|
non-régression Yellowstone -> identity/V1/reconnect/gap/hydration
|
||||||
|
security/redaction -> Transport/Worker/Common RAW/Store API/Store/PostgreSQL
|
||||||
|
```
|
||||||
|
|
||||||
|
Les suites `dependency_boundary`, `hardening`, `public_api` et `release_completeness` verrouillent explicitement la présence de cette matrice.
|
||||||
|
|
||||||
|
## Frontières
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun code production modifié
|
||||||
|
aucune API publique ajoutée
|
||||||
|
aucune dépendance/feature modifiée
|
||||||
|
aucun changement Transport/Common RAW/Store
|
||||||
|
aucun delta historique modifié
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation locale d'assemblage
|
||||||
|
|
||||||
|
Le toolchain Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Aucune commande Cargo post-modification n'est déclarée PASS localement.
|
||||||
|
|
||||||
|
Les audits statiques et les contrôles archive sont consignés uniquement après leur exécution effective.
|
||||||
|
|
||||||
|
## Gate opérateur vers pre.013
|
||||||
|
|
||||||
|
`pre.013` est le gate technique/live de fermeture ; exécuter :
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
Les smokes live accessibles sont qualifiés dans `pre.013`; un smoke inaccessible reste `NON EXÉCUTÉ`.
|
||||||
64
deltas/0.3.13/pre.013.md
Normal file
64
deltas/0.3.13/pre.013.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<!-- file: deltas/0.3.13/pre.013.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta 0.3.13-pre.013 — gate technique/live
|
||||||
|
|
||||||
|
## Objet
|
||||||
|
|
||||||
|
Matérialiser la tranche de gate technique/live prévue par le plan, sans ajouter de scope fonctionnel : enregistrer le gate déterministe workspace complet déjà communiqué et séparer strictement cette preuve des smokes live opt-in encore à exécuter.
|
||||||
|
|
||||||
|
## Gate déterministe reçu
|
||||||
|
|
||||||
|
Le gate `0.3.13-pre.012` du 10 septembre 2026 est vert pour les commandes exécutées :
|
||||||
|
|
||||||
|
```text
|
||||||
|
fmt : PASS
|
||||||
|
audits Rust : clean / export completeness 0
|
||||||
|
Markdown : clean, 340 tables / 850 files
|
||||||
|
cargo check --workspace : PASS
|
||||||
|
clippy workspace/all-targets/all-features -D warnings : PASS
|
||||||
|
cargo test --workspace --all-targets --all-features : 1 850 PASS / 0 échec / 15 ignored
|
||||||
|
Worker : 106 unit + 8 cross-layer + 19 dependency-boundary + 26 hardening + 20 public-api + 5 release-completeness, 0 échec
|
||||||
|
cargo tree Worker normal/features : exécutés sans erreur
|
||||||
|
cargo tree --duplicates : exécuté sans erreur ; inventaire multi-version affiché
|
||||||
|
```
|
||||||
|
|
||||||
|
Les tests `ignored` ne sont pas requalifiés en preuves live.
|
||||||
|
|
||||||
|
## Modifications
|
||||||
|
|
||||||
|
```text
|
||||||
|
workspace.package.version -> 0.3.13-pre.13
|
||||||
|
validation 030 : enregistrement du gate déterministe et qualification live
|
||||||
|
aucun Rust production/test modifié
|
||||||
|
aucune dépendance/feature modifiée
|
||||||
|
aucun delta historique modifié
|
||||||
|
```
|
||||||
|
|
||||||
|
## Smokes keyless requis pour fermer la partie live accessible
|
||||||
|
|
||||||
|
```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 smokes restent séparés du gate déterministe. Les preuves Yellowstone/Helius/blockSubscribe dédié/HTTP polling Worker/multi-source Store end-to-end restent `NON EXÉCUTÉ` tant qu'un gate réel ne fournit pas les credentials, provider capabilities ou ressources nécessaires.
|
||||||
|
|
||||||
|
## Frontières
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucun nouveau scope fonctionnel
|
||||||
|
aucune API publique modifiée
|
||||||
|
aucun comportement runtime modifié
|
||||||
|
aucun changement Transport/Store/Common RAW
|
||||||
|
aucun changement de dépendance ou feature
|
||||||
|
aucun delta historique modifié
|
||||||
|
```
|
||||||
|
|
||||||
|
## Validation locale d'assemblage
|
||||||
|
|
||||||
|
Le toolchain Cargo/Rustfmt n'est pas disponible dans l'environnement d'assemblage. Aucun PASS Cargo post-`pre.013` n'est revendiqué localement. Les audits statiques et contrôles archive sont déclarés uniquement après exécution effective.
|
||||||
|
|
||||||
|
## Gate opérateur
|
||||||
|
|
||||||
|
Après application du delta, exécuter les deux smokes keyless ci-dessus. En cas d'échec produit reproductible, rester sur `pre.013` et produire un fix. S'ils passent, les autres smokes non provisionnés restent explicitement `NON EXÉCUTÉ` et la release peut passer à `pre.014` pour la réconciliation documentaire.
|
||||||
78
deltas/0.3.13/pre.014.md
Normal file
78
deltas/0.3.13/pre.014.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<!-- file: deltas/0.3.13/pre.014.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.3.13-pre.014`
|
||||||
|
|
||||||
|
## Nature
|
||||||
|
|
||||||
|
Réconciliation documentaire de la verticale live multi-source `RawTransaction` après fermeture technique/live de `pre.013`.
|
||||||
|
|
||||||
|
Aucun code Rust, test, dépendance, feature ou contrat public n'est modifié.
|
||||||
|
|
||||||
|
## Gate d'entrée confirmé
|
||||||
|
|
||||||
|
Le gate déterministe `pre.012` reste vert et les deux smokes live keyless exécutés sur `0.3.13-pre.013` passent explicitement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Solana HTTP Devnet : 1/1 PASS
|
||||||
|
Solana WebSocket Devnet : 1/1 PASS
|
||||||
|
```
|
||||||
|
|
||||||
|
Le contrôle post-smoke communiqué passe également fmt, audits Rust/Markdown, `cargo check --workspace` et Clippy strict.
|
||||||
|
|
||||||
|
## Réconciliation durable
|
||||||
|
|
||||||
|
Les documents suivants sont mis en cohérence avec l'état réellement livré :
|
||||||
|
|
||||||
|
```text
|
||||||
|
docs/architecture/004-COMPONENT_INVENTORY.md
|
||||||
|
docs/architecture/005-DEPENDENCY_GRAPH.md
|
||||||
|
docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md
|
||||||
|
docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md
|
||||||
|
docs/plans/034-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE_PLAN.md
|
||||||
|
docs/validation/030-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Les corrections principales sont :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Worker RAW documenté comme composition de 1..32 sources logiques sur un même réseau
|
||||||
|
cinq familles live : Yellowstone / Standard Logs / Standard Block / Helius Transaction / HTTP Block Polling
|
||||||
|
registry globale d'hydration pour Yellowstone / Standard Logs / Helius
|
||||||
|
Standard Block et HTTP Block Polling RAW-direct Legacy/V0/V1
|
||||||
|
blockSubscribe actuel avec maxSupportedTransactionVersion = 1
|
||||||
|
convergence canonique + observations multiples + disagreement explicite
|
||||||
|
backpressure/fairness et bornes globales source-neutral
|
||||||
|
health multi-source conservative et shutdown/races bornés
|
||||||
|
reports EARLY / coverage-equivalence / repair historique maintenus hors 0.3.13
|
||||||
|
```
|
||||||
|
|
||||||
|
`README.md`, `crates/ksp-worker-raw-transaction-ingest-lib/README.md` et `USAGE.md` ont été audités et sont déjà cohérents ; ils restent byte-identiques. `USAGE.md` reste version-neutral.
|
||||||
|
|
||||||
|
## Frontières
|
||||||
|
|
||||||
|
Cette tranche ne modifie pas :
|
||||||
|
|
||||||
|
```text
|
||||||
|
CHANGELOG.md
|
||||||
|
ROADMAP.md
|
||||||
|
prompt 0.3.14
|
||||||
|
code/test Rust
|
||||||
|
Cargo dependencies/features
|
||||||
|
deltas historiques
|
||||||
|
```
|
||||||
|
|
||||||
|
La préparation publication reste réservée à `pre.015`.
|
||||||
|
|
||||||
|
## Gate opérateur avant `pre.015`
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun `cargo tree`, smoke live ou rerun exhaustif des tests n'est requis pour cette tranche strictement documentaire hors bump mécanique de prerelease.
|
||||||
224
deltas/0.3.13/pre.015.md
Normal file
224
deltas/0.3.13/pre.015.md
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
<!-- file: deltas/0.3.13/pre.015.md -->
|
||||||
|
<!-- version: 2 -->
|
||||||
|
|
||||||
|
# Delta `0.3.13-pre.015` — préparation de publication
|
||||||
|
|
||||||
|
## 1. Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.13-pre.014
|
||||||
|
workspace.package.version = 0.3.13-pre.14
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur communiqué le **10 septembre 2026 à 23:15** sur `pre.014` est vert pour toutes les commandes exécutées :
|
||||||
|
|
||||||
|
```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 / 852 files
|
||||||
|
cargo check --workspace : PASS
|
||||||
|
cargo clippy --workspace --all-targets --all-features -- -D warnings : PASS
|
||||||
|
```
|
||||||
|
|
||||||
|
Conformément à `pre.014`, aucun `cargo tree`, smoke live ou rerun exhaustif des tests n'était requis pour cette tranche strictement documentaire hors bump mécanique.
|
||||||
|
|
||||||
|
## 2. Objectif
|
||||||
|
|
||||||
|
`pre.015` est la dernière prerelease de préparation de publication de `0.3.13`.
|
||||||
|
|
||||||
|
Conformément à `VER-LIFECYCLE-003`, `VER-LIFECYCLE-012` et `PROMPT_STRUCTURE.md`, elle modifie fonctionnellement uniquement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
prompt de démarrage 0.3.14
|
||||||
|
CHANGELOG.md
|
||||||
|
ROADMAP.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Les seuls fichiers mécaniques supplémentaires sont :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml racine
|
||||||
|
delta pre.015
|
||||||
|
```
|
||||||
|
|
||||||
|
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.13-pre.14
|
||||||
|
->
|
||||||
|
0.3.13-pre.15
|
||||||
|
```
|
||||||
|
|
||||||
|
Le header de `Cargo.toml` passe de `563` à `564`.
|
||||||
|
|
||||||
|
## 4. Prompt suivant
|
||||||
|
|
||||||
|
Le nouveau prompt :
|
||||||
|
|
||||||
|
```text
|
||||||
|
prompts/033-V0_3_14_START_PROMPT.md
|
||||||
|
```
|
||||||
|
|
||||||
|
ouvre `0.3.14` exclusivement depuis le futur stable :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0.3.13
|
||||||
|
```
|
||||||
|
|
||||||
|
Sa mission est bornée au gap repair/hardening multi-source du run actif :
|
||||||
|
|
||||||
|
```text
|
||||||
|
replay natif réellement adressable
|
||||||
|
preuve de coverage par source redondante
|
||||||
|
scan HTTP de slots/blocs du run
|
||||||
|
hydration getTransaction de réparation
|
||||||
|
repaired vs unresolved gap explicites
|
||||||
|
health Healthy/Degraded/Unhealthy/Faulted fondée sur coverage prouvée
|
||||||
|
backpressure/fairness et shutdown pendant repair
|
||||||
|
smokes provider réellement accessibles
|
||||||
|
EARLY uniquement si pre.001 apporte une preuve actuelle suffisante
|
||||||
|
```
|
||||||
|
|
||||||
|
Le prompt interdit explicitement de transformer le Worker en campagne historique, de créer un edge Worker -> Job Backfill ou de coder le repair avant le `pre.001` d'audit/sizing. Il réserve :
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. CHANGELOG
|
||||||
|
|
||||||
|
`CHANGELOG.md` reçoit l'entrée stable `0.3.13` synthétisant :
|
||||||
|
|
||||||
|
```text
|
||||||
|
composition 1..32 sources d'un même réseau
|
||||||
|
cinq familles live productives
|
||||||
|
hydration globale Yellowstone / Standard Logs / Helius
|
||||||
|
RAW-direct Standard Block / HTTP Block Polling Legacy/V0/V1
|
||||||
|
coalescence cross-source + observations multiples
|
||||||
|
content conflict explicite sans first-provider-wins
|
||||||
|
fairness/backpressure/health source-neutral
|
||||||
|
shutdown/races hardening
|
||||||
|
gate déterministe 1 850 PASS / 0 échec / 15 ignored
|
||||||
|
smokes keyless HTTP Devnet + WebSocket Devnet PASS
|
||||||
|
preuves provider/resource-gated restantes NON EXÉCUTÉES
|
||||||
|
handoff 0.3.14 et prompt 033
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. ROADMAP
|
||||||
|
|
||||||
|
L'entrée `0.3.13` passe de `[ ]` à `[X]` et décrit l'état réellement livré. `0.3.14` reste ouverte pour le gap repair/hardening multi-source.
|
||||||
|
|
||||||
|
Le TODO Helius `0.3.13` passe également à `[X]` en documentant le résultat réel : la voie Helius Worker réutilise les profils Config et `KSP_SECRET_HELIUS_API_KEY` déjà existants ainsi que `transactionSubscribe` Transport-owned ; aucun nouveau profil, SDK, tier codé ou second client n'a été nécessaire.
|
||||||
|
|
||||||
|
## 7. Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
prompts/033-V0_3_14_START_PROMPT.md
|
||||||
|
deltas/0.3.13/pre.015.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/032-*
|
||||||
|
deltas/0.3.13/pre.001.md à pre.014.md et leurs fixes
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun delta historique n'est modifié.
|
||||||
|
|
||||||
|
## 11. Vérification complète de l'archive de base avant modification
|
||||||
|
|
||||||
|
L'archive opérateur `v0.3.13-pre.014` a été contrôlée avant assemblage :
|
||||||
|
|
||||||
|
```text
|
||||||
|
unzip -t : PASS
|
||||||
|
2 009 entrées ZIP
|
||||||
|
0 chemin absolu / traversal / entrée hors racine attendue
|
||||||
|
0 target/node_modules/dist/.git/.idea/__pycache__ embarqué
|
||||||
|
0 lockfile embarqué
|
||||||
|
rust-toolchain.toml absent
|
||||||
|
General Rust rule audit : clean
|
||||||
|
Rust export completeness audit : 0 candidate(s)
|
||||||
|
KSP workspace Rust rule audit : clean
|
||||||
|
Markdown table audit : clean, 340 tables / 852 files
|
||||||
|
5/5 tests unitaires du validateur Markdown : PASS
|
||||||
|
489 définitions de règles normatives
|
||||||
|
489 identifiants normatifs uniques
|
||||||
|
0 doublon d'identifiant normatif
|
||||||
|
headers/newline des surfaces critiques : PASS
|
||||||
|
scan high-confidence de secrets privés : aucun résultat
|
||||||
|
```
|
||||||
|
|
||||||
|
Le toolchain Cargo/Rustc n'est pas disponible dans l'environnement d'assemblage. Aucun PASS Cargo supplémentaire n'est revendiqué localement ; les PASS Cargo ci-dessus proviennent exclusivement du gate opérateur communiqué.
|
||||||
|
|
||||||
|
## 12. Validations exécutées sur l'état `pre.015`
|
||||||
|
|
||||||
|
Sur l'état exact de `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 / 854 files
|
||||||
|
489 définitions de règles / 489 identifiants uniques / 0 doublon
|
||||||
|
prompt 033 : 16 sections normatives présentes
|
||||||
|
diff exact pre.014 -> pre.015 : 3 fichiers existants modifiés + 2 ajoutés + 0 supprimé
|
||||||
|
headers des 3 fichiers existants : +1 exactement
|
||||||
|
Cargo.toml : hors header, seule workspace.package.version change
|
||||||
|
aucun README/USAGE/plan/validation/architecture/code/test/config/schema/dependency/feature modifié
|
||||||
|
```
|
||||||
|
|
||||||
|
Les contrôles `unzip -t`, archive safety et inventaire minimal du ZIP sont exécutés après packaging avant livraison.
|
||||||
|
|
||||||
|
Les commandes Cargo post-`pre.015` restent à exécuter par l'opérateur avant `rel.001` :
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
Clippy strict peut être rejoué ; aucun code/test/dépendance/feature n'étant modifié, il ne remplace pas le gate minimal ci-dessus.
|
||||||
|
|
||||||
|
## 13. Décisions prises
|
||||||
|
|
||||||
|
- `0.3.13` est fonctionnellement/documentairement fermé et prêt pour le gate de publication de `pre.015` ;
|
||||||
|
- `0.3.14` ne commence qu'après `0.3.13-rel.001` validée et le tag stable `v0.3.13` ;
|
||||||
|
- le prompt `0.3.14` conserve la séparation Worker/Backfill et impose un `pre.001` avant toute logique de repair ;
|
||||||
|
- les preuves live non exécutées de `0.3.13` restent explicitement non revendiquées ;
|
||||||
|
- aucun correctif d'un couloir technique/documentaire antérieur n'est absorbé dans cette préparation de publication.
|
||||||
|
|
||||||
|
## 14. Questions ouvertes
|
||||||
|
|
||||||
|
```text
|
||||||
|
aucune question bloquante pour la préparation de publication 0.3.13
|
||||||
|
```
|
||||||
190
deltas/0.3.13/rel.001.md
Normal file
190
deltas/0.3.13/rel.001.md
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
<!-- file: deltas/0.3.13/rel.001.md -->
|
||||||
|
<!-- version: 2 -->
|
||||||
|
|
||||||
|
# Delta `0.3.13-rel.001` — publication stable
|
||||||
|
|
||||||
|
## Base requise
|
||||||
|
|
||||||
|
Base directe attendue :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.13-pre.015
|
||||||
|
workspace.package.version = 0.3.13-pre.15
|
||||||
|
```
|
||||||
|
|
||||||
|
Les couloirs de fermeture précédents sont fermés :
|
||||||
|
|
||||||
|
- `pre.013` : gate technique déterministe complet, graphes Cargo et qualification explicite des smokes live ;
|
||||||
|
- smokes keyless post-`pre.013` : Solana HTTP Devnet `1/1 PASS` et WebSocket Devnet `1/1 PASS` ;
|
||||||
|
- `pre.014` : réconciliation documentaire finale de l'architecture, du plan et de la validation ;
|
||||||
|
- `pre.015` : préparation minimale de publication avec `CHANGELOG.md`, `ROADMAP.md` et `prompts/033-V0_3_14_START_PROMPT.md`.
|
||||||
|
|
||||||
|
Le gate opérateur reçu avant `pre.015`, exécuté le **10 septembre 2026 à 23:15** sur `0.3.13-pre.014`, est propre pour rustfmt, audits Rust/Markdown, `cargo check --workspace` et Clippy strict.
|
||||||
|
|
||||||
|
L'environnement d'assemblage de `pre.015` ne possède pas Cargo/Rustc. Le gate Cargo de l'état exact `0.3.13-pre.015` reste donc un **prérequis d'application** de ce delta et ne doit pas être déclaré PASS sans exécution opérateur réelle :
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
Si ce gate révèle un défaut, ne pas appliquer `rel.001` : ouvrir la prerelease/fix appropriée selon la responsabilité concernée.
|
||||||
|
|
||||||
|
## Objet
|
||||||
|
|
||||||
|
Publier mécaniquement la version stable :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.13
|
||||||
|
```
|
||||||
|
|
||||||
|
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
|
||||||
|
564 -> 565
|
||||||
|
```
|
||||||
|
|
||||||
|
La version workspace devient :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.13-pre.15 -> 0.3.13
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune autre ligne du `Cargo.toml` racine n'est modifiée.
|
||||||
|
|
||||||
|
## Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
deltas/0.3.13/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/033-V0_3_14_START_PROMPT.md
|
||||||
|
crates/**
|
||||||
|
docs/**
|
||||||
|
config/**
|
||||||
|
```
|
||||||
|
|
||||||
|
La surface préparée en `pre.015` reste byte-identique hors mécanique Cargo et ajout du présent delta.
|
||||||
|
|
||||||
|
## Surface stable publiée
|
||||||
|
|
||||||
|
`0.3.13` stabilise la convergence live multi-source de `ksp-worker-raw-transaction-ingest-lib` :
|
||||||
|
|
||||||
|
- composition caller-owned de `1` à `32` sources logiques d'un même réseau, avec identités dupliquées rejetées avant spawn ;
|
||||||
|
- cinq familles live productives : Yellowstone, Standard Logs, Standard Block, Helius Transaction et HTTP Block Polling ;
|
||||||
|
- Standard Logs et Helius Transaction convergent avec Yellowstone vers l'hydration globale `getTransaction observed` ;
|
||||||
|
- Standard Block et HTTP Block Polling utilisent le RAW-direct uniquement pour Legacy/V0/V1 qualifiés, avec `maxSupportedTransactionVersion = 1` ;
|
||||||
|
- coalescence cross-source de `(network, signature, commitment)` avant hydration et sérialisation bornée de l'acquisition canonique `(network, signature)` ;
|
||||||
|
- observations/provenances distinctes conservées pour un contenu canonique identique ; divergence convertie en `content_conflict` terminal, sans majorité, overwrite ou `first-provider-wins` ;
|
||||||
|
- backpressure, quotas et fairness source-neutral bornés entre sources reference-bearing, hydration et persistence ;
|
||||||
|
- snapshots/health agrégés sans exposition du matériau provider ; reconnexion transitoire `Degraded`, source `Failed` `Unhealthy`, retour à `Healthy` lorsque toutes les sources attendues redeviennent actives ;
|
||||||
|
- une faute source reste terminale en `0.3.13` faute de preuve d'équivalence de coverage ; le repair/failover non terminal appartient à `0.3.14` ;
|
||||||
|
- shutdown stop/fault/drain/abort+join durci contre leaders d'hydration abandonnés, Store lent, counters et publications tardives ;
|
||||||
|
- aucune dépendance Worker vers Config, Job Backfill ou backend Store physique, aucun client réseau/SDK provider parallèle et aucun second actor Transport.
|
||||||
|
|
||||||
|
Le gate technique déterministe de la release a exécuté `cargo test --workspace --all-targets --all-features` avec :
|
||||||
|
|
||||||
|
```text
|
||||||
|
1 850 PASS
|
||||||
|
0 échec
|
||||||
|
15 ignored opt-in/operator-only
|
||||||
|
```
|
||||||
|
|
||||||
|
Les smokes keyless Solana HTTP Devnet et WebSocket Devnet passent `1/1` chacun. Les preuves Yellowstone provider-gated, Helius `transactionSubscribe` live, `blockSubscribe` provider dédié, HTTP Block Polling Worker end-to-end et Worker multi-source -> Store end-to-end restent explicitement `NON EXÉCUTÉ` faute de gate provisionné ; aucune fixture ou compilation d'un test ignored ne les requalifie en PASS.
|
||||||
|
|
||||||
|
Le Job Backfill historique paramétré et le Worker Ingest continu restent deux producteurs indépendants du même Store. `0.3.13` n'introduit ni campagne historique automatique, ni checkpoint durable Worker, ni preuve de blockchain completeness.
|
||||||
|
|
||||||
|
## Suite préparée
|
||||||
|
|
||||||
|
La session suivante part exclusivement de la base stable :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0.3.13
|
||||||
|
```
|
||||||
|
|
||||||
|
et exécute :
|
||||||
|
|
||||||
|
```text
|
||||||
|
prompts/033-V0_3_14_START_PROMPT.md
|
||||||
|
```
|
||||||
|
|
||||||
|
pour ouvrir :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.14 — gap repair / hardening multi-source du Worker RawTransaction
|
||||||
|
```
|
||||||
|
|
||||||
|
`0.3.14-pre.001` doit commencer par lecture, audit interne/externe, brainstorming, sizing et planification avant toute implémentation de repair. Il doit distinguer reconnect, replay attempt, coverage prouvée, repair et unresolved gap ; le Worker ne doit toujours pas devenir un moteur de campagne historique ni dépendre de `ksp-job-backfill-lib`.
|
||||||
|
|
||||||
|
## 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 tables / 855 files
|
||||||
|
5/5 tests unitaires du validateur Markdown : PASS
|
||||||
|
489 définitions de règles / 489 identifiants uniques / 0 doublon
|
||||||
|
diff exact pre.015 -> rel.001 : Cargo.toml modifié + rel.001.md ajouté, 0 autre changement
|
||||||
|
Cargo.toml : exactement 2 lignes différentes, header 564 -> 565 et version 0.3.13-pre.15 -> 0.3.13
|
||||||
|
```
|
||||||
|
|
||||||
|
Les contrôles `unzip -t`, archive safety et inventaire minimal du ZIP sont exécutés après packaging avant livraison.
|
||||||
|
|
||||||
|
Aucun PASS Cargo post-bump stable n'est revendiqué localement faute de toolchain.
|
||||||
|
|
||||||
|
## Gate demandé après application
|
||||||
|
|
||||||
|
La tranche est purement mécanique. Après application sur un `pre.015` 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.13
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets --all-features -- -D warnings
|
||||||
|
cargo test -p ksp-worker-raw-transaction-ingest-lib
|
||||||
|
cargo test -p ksp-onchain-transport-lib
|
||||||
|
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.13-rel.001
|
||||||
|
tag : v0.3.13
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun tag de prerelease ou `rel.001` n'est requis.
|
||||||
218
deltas/0.3.14/pre.001.md
Normal file
218
deltas/0.3.14/pre.001.md
Normal 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 15–20 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 d’audit n’est 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`.
|
||||||
203
deltas/0.3.14/pre.002-fix.001.md
Normal file
203
deltas/0.3.14/pre.002-fix.001.md
Normal 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
|
||||||
|
```
|
||||||
220
deltas/0.3.14/pre.002-fix.002.md
Normal file
220
deltas/0.3.14/pre.002-fix.002.md
Normal 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
257
deltas/0.3.14/pre.002.md
Normal 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
333
deltas/0.3.14/pre.003.md
Normal 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
367
deltas/0.3.14/pre.004.md
Normal 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
320
deltas/0.3.14/pre.005.md
Normal 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
274
deltas/0.3.14/pre.006.md
Normal 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.
|
||||||
172
deltas/0.3.14/pre.007-fix.001.md
Normal file
172
deltas/0.3.14/pre.007-fix.001.md
Normal 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
217
deltas/0.3.14/pre.007.md
Normal 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.
|
||||||
172
deltas/0.3.14/pre.008-fix.001.md
Normal file
172
deltas/0.3.14/pre.008-fix.001.md
Normal 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
|
||||||
|
```
|
||||||
165
deltas/0.3.14/pre.008-fix.002.md
Normal file
165
deltas/0.3.14/pre.008-fix.002.md
Normal 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
285
deltas/0.3.14/pre.008.md
Normal 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.
|
||||||
178
deltas/0.3.14/pre.009-fix.001.md
Normal file
178
deltas/0.3.14/pre.009-fix.001.md
Normal 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
|
||||||
|
```
|
||||||
140
deltas/0.3.14/pre.009-fix.002.md
Normal file
140
deltas/0.3.14/pre.009-fix.002.md
Normal 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
270
deltas/0.3.14/pre.009.md
Normal 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
|
||||||
|
```
|
||||||
144
deltas/0.3.14/pre.010-fix.001.md
Normal file
144
deltas/0.3.14/pre.010-fix.001.md
Normal 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
273
deltas/0.3.14/pre.010.md
Normal 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
|
||||||
|
```
|
||||||
92
deltas/0.3.14/pre.011-fix.001.md
Normal file
92
deltas/0.3.14/pre.011-fix.001.md
Normal 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
|
||||||
|
```
|
||||||
102
deltas/0.3.14/pre.011-fix.002.md
Normal file
102
deltas/0.3.14/pre.011-fix.002.md
Normal 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
310
deltas/0.3.14/pre.011.md
Normal 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
237
deltas/0.3.14/pre.012.md
Normal 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
|
||||||
|
```
|
||||||
88
deltas/0.3.14/pre.013-fix.001.md
Normal file
88
deltas/0.3.14/pre.013-fix.001.md
Normal 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
294
deltas/0.3.14/pre.013.md
Normal 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
114
deltas/0.3.14/pre.014.md
Normal 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
194
deltas/0.3.14/pre.015.md
Normal 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
203
deltas/0.3.14/pre.016.md
Normal 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
195
deltas/0.3.14/rel.001.md
Normal 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.
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/architecture/004-COMPONENT_INVENTORY.md -->
|
<!-- file: docs/architecture/004-COMPONENT_INVENTORY.md -->
|
||||||
<!-- version: 39 -->
|
<!-- 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`, live `0.3.12` | fondation + Yellowstone productif, hydration HTTP, frontier/reconnect run-local |
|
| 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 d’une 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 d’une 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 |
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/architecture/005-DEPENDENCY_GRAPH.md -->
|
<!-- file: docs/architecture/005-DEPENDENCY_GRAPH.md -->
|
||||||
<!-- version: 28 -->
|
<!-- version: 30 -->
|
||||||
|
|
||||||
# Graphe de dépendances KSP
|
# Graphe de dépendances KSP
|
||||||
|
|
||||||
@@ -419,10 +419,10 @@ Il remplit RAW et ne décode aucun programme. Il ne dépend pas de Config : la c
|
|||||||
ksp-worker-api
|
ksp-worker-api
|
||||||
-> ksp-core-lib
|
-> ksp-core-lib
|
||||||
|
|
||||||
ksp-worker-raw-transaction-ingest-lib # fondation + première source live matérialisées
|
ksp-worker-raw-transaction-ingest-lib # fondation + acquisition live multi-source matérialisées
|
||||||
-> ksp-core-lib
|
-> ksp-core-lib
|
||||||
-> ksp-logging-lib
|
-> ksp-logging-lib
|
||||||
-> ksp-onchain-transport-lib # Yellowstone subscribe + HTTP getTransaction observed
|
-> ksp-onchain-transport-lib # Yellowstone, WS standard/Helius et HTTP observed/polling
|
||||||
-> ksp-raw-transaction-lib
|
-> ksp-raw-transaction-lib
|
||||||
-> ksp-store-lib # default-features = false ; aucun backend imposé
|
-> ksp-store-lib # default-features = false ; aucun backend imposé
|
||||||
-> ksp-worker-api
|
-> ksp-worker-api
|
||||||
@@ -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 ; l'extension live `0.3.12` ouvre l'edge direct vers `ksp-onchain-transport-lib` pour une source Yellowstone standard et l'hydration HTTP `getTransaction`. 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 exactement une source Yellowstone validée, les signaux transactionnels sont hydratés par HTTP avant Common RAW, et `BlockMeta`/`Slot` restent continuity-only. Reconnect, `from_slot` et `SubscribeReplayInfo` restent propriétaires de Transport ; le Worker observe leur projection sûre et fault sur un gap de rétention prouvé sans appeler le Job Backfill. Les extensions multi-source restent ultérieures.
|
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.
|
||||||
|
|
||||||
|
|||||||
@@ -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`
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md -->
|
<!-- file: docs/architecture/011-RAW_TRANSACTION_ACQUISITION.md -->
|
||||||
<!-- version: 13 -->
|
<!-- version: 15 -->
|
||||||
|
|
||||||
# Acquisition et alimentation `RawTransaction`
|
# Acquisition et alimentation `RawTransaction`
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@ Il peut utiliser **HTTP, WS, Yellowstone gRPC, replay provider ou archive** si c
|
|||||||
|
|
||||||
### 3.2 Worker Raw Transaction Ingest : acquisition continue start/stop
|
### 3.2 Worker Raw Transaction Ingest : acquisition continue start/stop
|
||||||
|
|
||||||
`ksp-worker-raw-transaction-ingest-lib` a pour rôle de **remplir continuellement Store à partir du moment où il est démarré**. Sa fondation runtime, l'admission, la canonicalisation Common RAW, la persistence et les snapshots sont complétés par une première source productive Yellowstone standard + hydration HTTP `getTransaction`.
|
`ksp-worker-raw-transaction-ingest-lib` a pour rôle de **remplir continuellement Store à partir du moment où il est démarré**. Sa fondation runtime, l'admission, la canonicalisation Common RAW, la persistence et les snapshots supportent désormais une composition live multi-source bornée de `1` à `32` sources logiques sur un même réseau.
|
||||||
|
|
||||||
Son contrôle métier V1 est volontairement court :
|
Son contrôle métier V1 est volontairement court :
|
||||||
|
|
||||||
@@ -161,22 +161,33 @@ snapshot / notifications
|
|||||||
|
|
||||||
Le caller ne lui fournit pas une signature, un `program_id`, une plage historique, une limite de campagne ou une requête de backfill. Les endpoints, réseaux, sources activées, capabilities et secrets proviennent de Config/composition, pas d'un payload métier de `start`.
|
Le caller ne lui fournit pas une signature, un `program_id`, une plage historique, une limite de campagne ou une requête de backfill. Les endpoints, réseaux, sources activées, capabilities et secrets proviennent de Config/composition, pas d'un payload métier de `start`.
|
||||||
|
|
||||||
Deux entrées publiques coexistent : `start` conserve la fondation sans source productive, tandis que `start_with_runtime_resources` lance une source Yellowstone supervisée. Le pipeline productif actuel est :
|
Deux entrées publiques coexistent : `start` conserve la fondation sans source productive, tandis que `start_with_runtime_resources` lance la collection de ressources live validée. Les cinq familles productives matérialisées sont :
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Yellowstone Transaction / TransactionStatus / Block
|
Yellowstone Transaction / TransactionStatus / Block
|
||||||
|
Standard WS logsSubscribe
|
||||||
|
Helius transactionSubscribe
|
||||||
-> signaux transactionnels source-neutral
|
-> signaux transactionnels source-neutral
|
||||||
-> coalescence bornée (network, signature, commitment)
|
-> registry globale d'hydration bornée (network, signature, commitment)
|
||||||
-> HTTP getTransaction observed
|
-> HTTP getTransaction observed
|
||||||
-> Common RAW
|
-> Common RAW
|
||||||
-> admission centrale
|
|
||||||
|
Standard WS blockSubscribe Full/Base64 Legacy|V0|V1
|
||||||
|
HTTP live block polling getSlot -> getBlocksWithLimit -> getBlock observed
|
||||||
|
-> qualification RAW directe Legacy|V0|V1
|
||||||
|
-> Common RAW
|
||||||
|
|
||||||
|
Toutes les voies
|
||||||
|
-> admission centrale bornée
|
||||||
|
-> convergence canonique (network, signature)
|
||||||
|
-> RawTransaction + observations de provenance distinctes
|
||||||
-> Store
|
-> Store
|
||||||
|
|
||||||
Yellowstone BlockMeta / Slot
|
Yellowstone BlockMeta / Slot
|
||||||
-> continuité run-local uniquement
|
-> continuité run-local uniquement
|
||||||
```
|
```
|
||||||
|
|
||||||
La source est caller-composed : le Worker ne lit pas Config et ne reçoit pas de secret. `RawTransactionIngestRuntimeResources` contient actuellement exactement une source Yellowstone validée, pas encore une collection multi-source.
|
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.
|
||||||
|
|
||||||
@@ -193,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
|
||||||
|
|
||||||
@@ -253,7 +267,7 @@ Une source concrète peut fournir plusieurs capabilities. Une capability peut ê
|
|||||||
| HTTP `getSlot` / `getFirstAvailableBlock` / `minimumLedgerSlot` | bornes de ledger | non | oui, continuité | oui, admission d'une campagne | aucune transaction directe |
|
| HTTP `getSlot` / `getFirstAvailableBlock` / `minimumLedgerSlot` | bornes de ledger | non | oui, continuité | oui, admission d'une campagne | aucune transaction directe |
|
||||||
| WS `logsSubscribe` | signature + logs | non | oui, discovery live + hydration | non pour historique pur | `mentions` standard limité à un pubkey |
|
| WS `logsSubscribe` | signature + logs | non | oui, discovery live + hydration | non pour historique pur | `mentions` standard limité à un pubkey |
|
||||||
| WS `signatureSubscribe` | statut d'une signature connue | non | oui, confirmation ciblée interne | possible pour une requête ciblée en attente | one-shot |
|
| WS `signatureSubscribe` | statut d'une signature connue | non | oui, confirmation ciblée interne | possible pour une requête ciblée en attente | one-shot |
|
||||||
| WS `blockSubscribe` full/base64, legacy-v0 | bloc + transactions | oui, parité RAW v1 prouvée | oui, direct pour le sous-ensemble qualifié | non sans mécanisme de replay historique | méthode standard instable ; capability validator-dependent |
|
| WS `blockSubscribe` full/base64, Legacy/V0/V1 | bloc + transactions | oui, Legacy/V0/V1 qualifiés | oui, direct pour le sous-ensemble qualifié | non sans mécanisme de replay historique | méthode standard instable ; capability validator-dependent |
|
||||||
| Helius `transactionSubscribe` full/base64 | transaction + meta + signature/index | non sans hydration | oui, signal riche puis hydration HTTP | non comme source historique | absence de blockTime/version dans l’enveloppe qualifiée |
|
| Helius `transactionSubscribe` full/base64 | transaction + meta + signature/index | non sans hydration | oui, signal riche puis hydration HTTP | non comme source historique | absence de blockTime/version dans l’enveloppe qualifiée |
|
||||||
| Yellowstone `transactions` | transaction exécutée + meta | oui | oui | oui si replay borné demandé/disponible | filters server-side |
|
| Yellowstone `transactions` | transaction exécutée + meta | oui | oui | oui si replay borné demandé/disponible | filters server-side |
|
||||||
| Yellowstone `blocks` avec transactions | bloc + transactions | oui | oui | oui si replay borné demandé/disponible | utile au live et au backfill |
|
| Yellowstone `blocks` avec transactions | bloc + transactions | oui | oui | oui si replay borné demandé/disponible | utile au live et au backfill |
|
||||||
@@ -274,15 +288,17 @@ Cette matrice est intentionnellement **usage-first**. HTTP n'est pas « Backfill
|
|||||||
|
|
||||||
### 6.1 Acquisition directe
|
### 6.1 Acquisition directe
|
||||||
|
|
||||||
Sources capables de produire directement un matériau transactionnel complet :
|
Sources capables de produire directement un matériau transactionnel complet dans l'architecture générale :
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Yellowstone transactions
|
Yellowstone transactions/blocks lorsque l'adapter choisi qualifie directement leur wire
|
||||||
Yellowstone blocks avec transactions
|
WS blockSubscribe full/base64 Legacy/V0/V1 qualifié
|
||||||
WS blockSubscribe full/base64 legacy-v0 qualifié
|
HTTP getBlock full/base64 Legacy/V0/V1
|
||||||
provider stream compatible full transaction après parité prouvée
|
provider stream compatible full transaction après parité prouvée
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Dans le Worker V1 actuel, Yellowstone reste volontairement traité comme signal puis hydraté par HTTP `getTransaction` afin de conserver un seul chemin de canonicalisation productif pour cette famille. Les voies directes effectivement matérialisées dans le Worker sont Standard Block et HTTP Block Polling.
|
||||||
|
|
||||||
Chemin logique :
|
Chemin logique :
|
||||||
|
|
||||||
```text
|
```text
|
||||||
@@ -567,7 +583,7 @@ Helius transactionSubscribe
|
|||||||
|
|
||||||
Le runtime WS possède des queues bornées et une logique de reconnect/resubscribe. Un reconnect ne constitue toutefois pas un replay adressable.
|
Le runtime WS possède des queues bornées et une logique de reconnect/resubscribe. Un reconnect ne constitue toutefois pas un replay adressable.
|
||||||
|
|
||||||
La qualification `pre.005` ferme deux cas distincts : `blockSubscribe` standard en `full/base64` avec `maxSupportedTransactionVersion = 0` produit, sur fixture identique, les mêmes bytes/hash RAW v1 que `getBlock`; Helius `transactionSubscribe` full/base64 conserve l’identité, le slot, le transaction wire, la meta et l’index mais ne transporte pas `blockTime` ni `version` dans l’enveloppe qualifiée. Helius reste donc un signal live riche à hydrater par HTTP avant persistence RAW directe.
|
La qualification actuelle ferme deux cas distincts : `blockSubscribe` standard en `full/base64` avec `maxSupportedTransactionVersion = 1` qualifie explicitement Legacy/V0/V1 et produit le matériau Common RAW directement ; Helius `transactionSubscribe` full/base64 conserve l’identité, le slot, le transaction wire, la meta et l’index mais ne transporte pas `blockTime` ni `version` dans l’enveloppe qualifiée. Helius reste donc un signal live riche à hydrater par HTTP avant persistence RAW.
|
||||||
|
|
||||||
### 12.3 Yellowstone gRPC
|
### 12.3 Yellowstone gRPC
|
||||||
|
|
||||||
@@ -721,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.12`
|
## 14. État du Worker live après `0.3.14`
|
||||||
|
|
||||||
### 14.0 Verticale matérialisée
|
### 14.0 Verticale matérialisée
|
||||||
|
|
||||||
@@ -731,20 +747,27 @@ La verticale productive possède :
|
|||||||
consumer de ksp-worker-api
|
consumer de ksp-worker-api
|
||||||
settings réseau/Worker bornés
|
settings réseau/Worker bornés
|
||||||
start/stop sur runtime Tokio caller-owned
|
start/stop sur runtime Tokio caller-owned
|
||||||
supervision privée des tâches
|
supervision privée de 1..32 sources logiques sur un même réseau
|
||||||
|
cinq familles live : Yellowstone / Standard Logs / Standard Block / Helius Transaction / HTTP Block Polling
|
||||||
mpsc central borné + backpressure
|
mpsc central borné + backpressure
|
||||||
une source Yellowstone standard caller-composed
|
registry globale d'hydration pour Yellowstone / Standard Logs / Helius
|
||||||
Transaction / TransactionStatus / Block -> signaux transactionnels
|
qualification RAW directe pour Standard Block / HTTP Block Polling
|
||||||
BlockMeta / Slot -> continuity-only
|
|
||||||
coalescence bornée par network/signature/commitment
|
coalescence bornée par network/signature/commitment
|
||||||
HTTP getTransaction observed pour hydration
|
HTTP getTransaction observed pour hydration
|
||||||
canonicalisation et assembly via ksp-raw-transaction-lib
|
canonicalisation et assembly via ksp-raw-transaction-lib
|
||||||
persistence atomique via ksp-store-lib en mode Normal
|
convergence canonique par (network, signature) + disagreement explicite
|
||||||
processing frontier run-local bornée
|
persistence atomique via ksp-store-lib en mode Normal + observations supplémentaires idempotentes
|
||||||
projection source Active/Reconnecting/Closing/Closed/Failed
|
quotas pending/in-flight par source et bornes globales exactes
|
||||||
observation des compteurs reconnect/replay/gap Transport
|
fairness minimale / anti-starvation sous duplicate storm
|
||||||
fault sur gap de rétention prouvé
|
processing frontier et continuity frontier run-local distinctes
|
||||||
shutdown deadline + abort/join sans tâche orpheline
|
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
|
||||||
|
health Healthy/Degraded/Unhealthy fondée sur gaps, frontiers et coverage présente/future
|
||||||
|
fairness nominal/repair sur les mêmes admission/hydration/persistence
|
||||||
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
Elle conserve explicitement les frontières suivantes :
|
Elle conserve explicitement les frontières suivantes :
|
||||||
@@ -773,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 :
|
||||||
@@ -786,21 +810,31 @@ attendre un Job pour continuer
|
|||||||
hardcoder un provider unique
|
hardcoder un provider unique
|
||||||
```
|
```
|
||||||
|
|
||||||
### 14.2 Sources Worker V1 à représenter
|
### 14.2 Sources Worker V1 matérialisées et reports
|
||||||
|
|
||||||
À implémenter autant que possible, même si certaines preuves live restent bloquées par tier :
|
Matérialisé dans `0.3.13` puis durci dans `0.3.14` :
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Yellowstone transactions
|
Yellowstone transactions / blocks / status + HTTP hydration
|
||||||
Yellowstone blocks
|
|
||||||
Yellowstone status + hydration
|
|
||||||
WS logsSubscribe + HTTP getTransaction
|
WS logsSubscribe + HTTP getTransaction
|
||||||
WS blockSubscribe full/base64 legacy-v0 direct qualifié
|
WS blockSubscribe Full/Base64 Legacy/V0/V1 direct qualifié
|
||||||
Helius transactionSubscribe full/base64 + HTTP hydration
|
Helius transactionSubscribe Full + HTTP hydration
|
||||||
HTTP live block polling
|
HTTP live block polling getSlot/getBlocksWithLimit/getBlock observed
|
||||||
HTTP hydration
|
HTTP hydration partagée et coalescée cross-source
|
||||||
replay Yellowstone pour continuité du run
|
replay Yellowstone conservé propriétaire de Transport pour continuité du run
|
||||||
sources EARLY via adapter extensible
|
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 :
|
||||||
|
|
||||||
|
```text
|
||||||
|
sources EARLY / pre-execution
|
||||||
|
campagne historique multi-source ou caller-paramétrable
|
||||||
|
Worker multi-source -> Store live E2E avec environnement complet provisionné
|
||||||
```
|
```
|
||||||
|
|
||||||
### 14.3 Gaps Transport Worker
|
### 14.3 Gaps Transport Worker
|
||||||
@@ -858,9 +892,9 @@ Ordre conseillé :
|
|||||||
0.3.11 P0 : Worker foundation/runtime + persistence déterministe
|
0.3.11 P0 : Worker foundation/runtime + persistence déterministe
|
||||||
0.3.12 P0 : Yellowstone transactions/blocks/status + hydration + replay continuity
|
0.3.12 P0 : Yellowstone transactions/blocks/status + hydration + replay continuity
|
||||||
0.3.13 P0 : WS logs + hydration, blockSubscribe, Helius transactionSubscribe, HTTP live polling
|
0.3.13 P0 : WS logs + hydration, blockSubscribe, Helius transactionSubscribe, HTTP live polling
|
||||||
0.3.13 P1 : multi-source convergence, dedup, provenance, content conflict, backpressure
|
0.3.13 P1 : multi-source convergence, dedup, provenance, content conflict, backpressure/fairness
|
||||||
0.3.14 P0 : continuity repair complet + hardening + smokes gratuits accessibles
|
0.3.13 P2 : health source-neutral, shutdown/races, completeness/security et gate live keyless
|
||||||
0.3.14 P2 : EARLY adapters accessibles seulement si prouvés
|
0.3.14 : trajectoire suivante définie par son prompt dédié ; ne pas réintroduire implicitement un scope reporté
|
||||||
0.3.16 P0 : block scan historique
|
0.3.16 P0 : block scan historique
|
||||||
0.3.16 P0 : replay Yellowstone borné
|
0.3.16 P0 : replay Yellowstone borné
|
||||||
0.3.16 P1 : provider history/archive
|
0.3.16 P1 : provider history/archive
|
||||||
@@ -984,7 +1018,8 @@ Yellowstone pre.006 = transaction wire V1 qualifié ; RAW complet via h
|
|||||||
TR-C2 = adapter productif Transport DTO -> common réservé au Worker concret
|
TR-C2 = adapter productif Transport DTO -> common réservé au Worker concret
|
||||||
0.3.10 = common RAW + preuves cross-source
|
0.3.10 = common RAW + preuves cross-source
|
||||||
0.3.11 = fondation Worker source-neutral, persistence et observabilité
|
0.3.11 = fondation Worker source-neutral, persistence et observabilité
|
||||||
0.3.12 à 0.3.14 = sources live multi-source, continuité et hardening par responsabilités bornées
|
0.3.12 = première verticale Yellowstone + hydration/replay continuity
|
||||||
|
0.3.13 = cinq familles live + convergence/fairness/health/shutdown/completeness
|
||||||
0.3.16 = Job Backfill multi-stratégie historique
|
0.3.16 = Job Backfill multi-stratégie historique
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md -->
|
<!-- file: docs/plans/002-FUNCTIONAL_RELEASE_SEQUENCE.md -->
|
||||||
<!-- version: 98 -->
|
<!-- version: 100 -->
|
||||||
|
|
||||||
# Séquence des releases fonctionnelles KSP
|
# Séquence des releases fonctionnelles KSP
|
||||||
|
|
||||||
@@ -583,7 +583,9 @@ La séquence effective a évolué par sizing et validation réels. La référenc
|
|||||||
0.3.9 ksp-worker-api + audit acquisition RawTransaction
|
0.3.9 ksp-worker-api + audit acquisition RawTransaction
|
||||||
0.3.10 common ksp-raw-transaction-lib + preuves cross-source
|
0.3.10 common ksp-raw-transaction-lib + preuves cross-source
|
||||||
0.3.11 fondation source-neutral ksp-worker-raw-transaction-ingest-lib
|
0.3.11 fondation source-neutral ksp-worker-raw-transaction-ingest-lib
|
||||||
0.3.12-0.3.14 sources live, convergence et continuité Worker RAW ingest
|
0.3.12 première verticale live Yellowstone + hydration/replay continuity
|
||||||
|
0.3.13 cinq familles live + convergence multi-source/fairness/health/hardening
|
||||||
|
0.3.14 tranche suivante de la trajectoire RAW selon prompt dédié
|
||||||
0.3.15 ksp-app-raw-transaction-ingest-desk
|
0.3.15 ksp-app-raw-transaction-ingest-desk
|
||||||
0.3.16 Backfill multi-source / multi-stratégie
|
0.3.16 Backfill multi-source / multi-stratégie
|
||||||
```
|
```
|
||||||
|
|||||||
1719
docs/plans/034-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE_PLAN.md
Normal file
1719
docs/plans/034-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE_PLAN.md
Normal file
File diff suppressed because it is too large
Load Diff
1098
docs/plans/035-V0_3_14_MULTI_SOURCE_GAP_REPAIR_HARDENING_PLAN.md
Normal file
1098
docs/plans/035-V0_3_14_MULTI_SOURCE_GAP_REPAIR_HARDENING_PLAN.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/rules/RULES_KSP.md -->
|
<!-- file: docs/rules/RULES_KSP.md -->
|
||||||
<!-- version: 41 -->
|
<!-- version: 42 -->
|
||||||
|
|
||||||
# Règles spécifiques à KSP
|
# Règles spécifiques à KSP
|
||||||
|
|
||||||
@@ -45,8 +45,8 @@
|
|||||||
- **KSP-CONFIG-015** — L'environnement du processus reste read-only. La surface management peut créer/modifier/supprimer uniquement des entrées KSP/KSPB du `.env`; chaque mutation rapporte séparément changement de source persistée, changement effectif courant, shadowing par le process et besoin de reload.
|
- **KSP-CONFIG-015** — L'environnement du processus reste read-only. La surface management peut créer/modifier/supprimer uniquement des entrées KSP/KSPB du `.env`; chaque mutation rapporte séparément changement de source persistée, changement effectif courant, shadowing par le process et besoin de reload.
|
||||||
- **KSP-CONFIG-016** — Les rapports management ordinaires n'exposent que des valeurs sûres/redacted. L'accès en clair à une valeur d'environnement passe par un appel `reveal_*` explicite; l'authentification/autorisation de l'utilisateur final appartient à l'application et cette révélation n'autorise jamais le secret dans les logs/`Debug`/diagnostics génériques.
|
- **KSP-CONFIG-016** — Les rapports management ordinaires n'exposent que des valeurs sûres/redacted. L'accès en clair à une valeur d'environnement passe par un appel `reveal_*` explicite; l'authentification/autorisation de l'utilisateur final appartient à l'application et cette révélation n'autorise jamais le secret dans les logs/`Debug`/diagnostics génériques.
|
||||||
- **KSP-CONFIG-017** — Les frontières d'ownership Config sont vérifiées par des audits exécutables du workspace : Core/Logging ne dépendent pas de Config, les crates hors `ksp-config-lib` ne lisent pas directement les variables KSP/KSPB via `std::env::var*`/énumération de l'environnement et ne codent pas en dur les noms physiques des fichiers gérés lorsqu'un contrat Config existe.
|
- **KSP-CONFIG-017** — Les frontières d'ownership Config sont vérifiées par des audits exécutables du workspace : Core/Logging ne dépendent pas de Config, les crates hors `ksp-config-lib` ne lisent pas directement les variables KSP/KSPB via `std::env::var*`/énumération de l'environnement et ne codent pas en dur les noms physiques des fichiers gérés lorsqu'un contrat Config existe.
|
||||||
- **KSP-CONFIG-018** — Dans un runtime desktop packagé, `ksp-config-lib` possède la préparation de la racine KSP writable à partir des resources applicatives : les documents Config packagés ne sont seedés que lorsqu'ils sont absents, les JSON Schemas possédés par le package courant sont resynchronisés à chaque lancement, et `.env` n'est jamais embarqué ni seedé. La localisation physique writable est résolue par une primitive plateforme dédiée et n'est pas codée en dur dans les applications.
|
|
||||||
- **KSP-CONFIG-018** — L'inventaire `.env.example` est vérifié automatiquement contre les variables KSP/KSPB concrètes utilisées par les JSON sous `config/` et le code Rust production. Toute clé runtime détectée doit posséder une entrée d'inventaire précédée d'un commentaire explicatif.
|
- **KSP-CONFIG-018** — L'inventaire `.env.example` est vérifié automatiquement contre les variables KSP/KSPB concrètes utilisées par les JSON sous `config/` et le code Rust production. Toute clé runtime détectée doit posséder une entrée d'inventaire précédée d'un commentaire explicatif.
|
||||||
|
- **KSP-CONFIG-019** — Dans un runtime desktop packagé, `ksp-config-lib` possède la préparation de la racine KSP writable à partir des resources applicatives : les documents Config packagés ne sont seedés que lorsqu'ils sont absents, les JSON Schemas possédés par le package courant sont resynchronisés à chaque lancement, et `.env` n'est jamais embarqué ni seedé. La localisation physique writable est résolue par une primitive plateforme dédiée et n'est pas codée en dur dans les applications.
|
||||||
|
|
||||||
## Programmes et exécution
|
## Programmes et exécution
|
||||||
|
|
||||||
|
|||||||
1785
docs/validation/030-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE.md
Normal file
1785
docs/validation/030-V0_3_13_MULTI_SOURCE_LIVE_CONVERGENCE.md
Normal file
File diff suppressed because it is too large
Load Diff
798
docs/validation/031-V0_3_14_MULTI_SOURCE_GAP_REPAIR_HARDENING.md
Normal file
798
docs/validation/031-V0_3_14_MULTI_SOURCE_GAP_REPAIR_HARDENING.md
Normal 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`.
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: prompts/032-V0_3_13_START_PROMPT.md -->
|
<!-- file: prompts/032-V0_3_13_START_PROMPT.md -->
|
||||||
<!-- version: 1 -->
|
<!-- version: 2 -->
|
||||||
|
|
||||||
# Prompt de démarrage `0.3.13` — WS standard / Helius / HTTP live + convergence multi-source du Worker `RawTransaction`
|
# Prompt de démarrage `0.3.13` — WS standard / Helius / HTTP live + convergence multi-source du Worker `RawTransaction`
|
||||||
|
|
||||||
@@ -555,7 +555,7 @@ Si Config/profils Helius sont modifiés :
|
|||||||
cargo test -p ksp-config-lib
|
cargo test -p ksp-config-lib
|
||||||
```
|
```
|
||||||
|
|
||||||
Graphes à maintenir/auditer :
|
Graphes à auditer uniquement si le graphe de dépendances/features change, ou à la fermeture technique :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cargo tree -p ksp-worker-raw-transaction-ingest-lib --edges normal
|
cargo tree -p ksp-worker-raw-transaction-ingest-lib --edges normal
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user