v0.3.6-pre.001
This commit is contained in:
414
docs/plans/027-V0_3_6_JOB_API_BACKFILL_PLAN.md
Normal file
414
docs/plans/027-V0_3_6_JOB_API_BACKFILL_PLAN.md
Normal file
@@ -0,0 +1,414 @@
|
||||
<!-- file: docs/plans/027-V0_3_6_JOB_API_BACKFILL_PLAN.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Plan v0.3.6 — Job API et premier backfill RAW
|
||||
|
||||
## 1. But de la version
|
||||
|
||||
La v0.3.6 ouvre la famille des Jobs avec deux crates et un premier vertical réel :
|
||||
|
||||
- `ksp-job-api`, contrat public passif et runtime-neutral de cycle de vie, annulation et observation latest-value ;
|
||||
- `ksp-job-backfill-lib`, orchestration concrète d'un backfill historique borné ;
|
||||
- découverte par `getSignaturesForAddress`, hydratation par `getTransaction`, conversion RAW déterministe et persistance par `ksp-store-lib` ;
|
||||
- preuve de progression, annulation coopérative, reprise par frontière contiguë et idempotence logique.
|
||||
|
||||
Cette version ne crée ni Worker API, ni application, ni scheduler général, ni pipeline RAW partagé prématuré.
|
||||
|
||||
## 2. Audit préalable et hiérarchie des sources
|
||||
|
||||
L'audit `pre.001` a été conduit avant toute création de crate. Les archives source et de référence ont été testées intégralement, puis extraites dans des arbres séparés. Les règles canoniques du dépôt priment sur les chemins abrégés du prompt : les fichiers effectivement présents sous `docs/rules/` sont les autorités applicables.
|
||||
|
||||
Sources lues :
|
||||
|
||||
- `RULES.md`, `docs/000-README.md` et les règles sous `docs/rules/` ;
|
||||
- `docs/architecture/002-LAYERS_AND_DEPENDENCIES.md` à `005-PERSISTENCE_ARCHITECTURE.md`, puis `009-JOB_AND_WORKER_ARCHITECTURE.md` ;
|
||||
- `ROADMAP.md`, `CHANGELOG.md` et les deltas antérieurs ;
|
||||
- surfaces Rust réelles de Core, Transport et Store ;
|
||||
- documentation RPC officielle Solana pour `getSignaturesForAddress` et `getTransaction` ;
|
||||
- kbot3 v0.5.3-pre.005-fix010, uniquement comme inventaire fonctionnel et corpus de scénarios.
|
||||
|
||||
Constats structurants :
|
||||
|
||||
- le dépôt est stable en v0.3.5 et ne contient encore aucune crate Job ;
|
||||
- les documents historiques emploient encore parfois `ksp-job-backfill`, alors que le nom canonique fixé par le prompt est `ksp-job-backfill-lib` ;
|
||||
- `ksp-onchain-transport-lib` fournit déjà les deux wrappers typés, le routage, les délais, la limitation et les retries ;
|
||||
- le retour de `getTransaction` ne transporte pas encore l'identité de l'endpoint réellement retenu, alors que la provenance Store exige au minimum un fournisseur ;
|
||||
- `ksp-store-lib` réexporte le contrat Store, fournit l'écriture atomique transaction plus observation et doit rester l'unique accès Job au Store ;
|
||||
- aucune règle n'autorise une réutilisation de code kbot3. Aucun fichier, extrait ou algorithme n'est copié depuis cette archive.
|
||||
|
||||
## 3. Périmètre retenu et non-objectifs
|
||||
|
||||
Dans le périmètre :
|
||||
|
||||
- identifiants et états de Job ;
|
||||
- source d'observation multi-listeners à valeur la plus récente ;
|
||||
- token d'annulation indépendant de Tokio ;
|
||||
- quatre portées de backfill : dernière fenêtre d'une adresse, avant une ancre, après une ancre et signatures explicites ;
|
||||
- pagination bornée, déduplication stable, hydratation bornée et persistance RAW ;
|
||||
- résultat observable, compteurs structurés, checkpoint et frontière contiguë ;
|
||||
- tests unitaires, dépendances interdites, canaries externes et intégration déterministe.
|
||||
|
||||
Hors périmètre :
|
||||
|
||||
- `ksp-worker-api`, application desktop, CLI, configuration utilisateur et ordonnanceur ;
|
||||
- décodage métier des transactions, normalisation SPL ou indexation sémantique ;
|
||||
- journal persistant des tentatives RPC manquantes ou échouées ;
|
||||
- callbacks producteurs, file de notifications non bornée et historique public complet ;
|
||||
- retry, pacing, sélection d'endpoint ou requête HTTP parallèles au Transport ;
|
||||
- backend Store direct, `ForceRehydrate` et prélecture de présence avant hydratation ;
|
||||
- extraction d'une crate pipeline partagée avant l'arrivée d'un second hôte réel.
|
||||
|
||||
## 4. Matrice de décision kbot3
|
||||
|
||||
Le tableau décrit des comportements observés ; il ne constitue pas une filiation de code.
|
||||
|
||||
| Fonction historique | Preuve fichier, test ou run | Sémantique réelle | Statut KSP | Owner KSP cible | Risque ou différence actuelle | Préversion cible |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| Sources latest, before et after | `ks-pipeline/src/backfill.rs`, `collect_before_candidates`, `collect_after_candidates` | Pagination d'adresse, ancre exclusive et fenêtre plus récente proche | REPRENDRE | Backfill avec wrappers Transport | L'ordre after doit être figé et l'ancre non atteinte observable | `pre.005`, `pre.008` |
|
||||
| Signatures explicites | `explicit_candidates_are_deduplicated_in_input_order` | Première occurrence conservée et ordre d'entrée stable | REPRENDRE | Backfill | Validation Base58 et limite absentes du type KSP actuel | `pre.005` |
|
||||
| Déduplication inter-pages | `nearest_newer_candidates_deduplicate_page_entries` | Un candidat identique n'est admis qu'une fois | REPRENDRE | Backfill | Doit couvrir toutes les directions, pas seulement after | `pre.005` |
|
||||
| Fenêtre after la plus proche | `nearest_newer_candidates_keep_only_entries_closest_to_anchor` | Retient les N signatures plus récentes les plus proches de l'ancre | REPRENDRE | Backfill | Nécessite une preuve officielle et un ordre KSP déterministe | `pre.005` |
|
||||
| Frontière contiguë | `completion_frontier_advances_only_across_contiguous_results` | Une fin hors ordre ne saute pas un candidat incomplet | REPRENDRE | Backfill | Missing et erreurs ne doivent pas être marqués durables | `pre.008` |
|
||||
| Reprise before | `cancelled_before_resume_uses_last_contiguous_candidate`, `cancelled_before_resume_keeps_anchor_when_nothing_completed` | Curseur repris au dernier préfixe contigu ou à l'ancre initiale | REPRENDRE | Backfill et hôte pour garde durable | Le checkpoint doit être lié au JobId et au scope | `pre.008` |
|
||||
| Reprise latest | `cancelled_latest_scan_without_completed_candidate_restarts_from_latest` | Une vue latest vide de complétion repart sans curseur | REPRENDRE | Backfill | Le replay doit dépendre de l'idempotence Store | `pre.008` |
|
||||
| Annulation d'attente et RPC long | `cancellable_retry_wait_returns_immediately_when_already_cancelled`, `long_running_rpc_future_is_cancelled_cooperatively` | Une sélection coopérative abandonne le futur non durable | REPRENDRE | Job API pour token, Backfill pour runtime | Un commit Store soumis ne peut pas être abandonné aveuglément | `pre.002`, `pre.009` |
|
||||
| Taxonomie Program, Token ou Pool | `BackfillAddressKind`, `filter_code_distinguishes_target_and_direction` | Étiquette UI intégrée au filtre historique | REDESSINER | Application v0.3.7 | Une adresse Core ne porte pas ce domaine de présentation | v0.3.7 |
|
||||
| Callback `BackfillObserver` | `ks-pipeline/src/backfill.rs`, `DemoBackfillObserver` dans l'application | Le producteur appelle directement un consumer et émet un message | REDESSINER | Job API et snapshot concret Backfill | Blocage, panic consumer et chaînes humaines possibles | `pre.003`, `pre.009` |
|
||||
| Compteurs `existing_skipped`, `canonical_skipped`, `missing`, `failed` | `BackfillSummary` et `summary_payload` | Plusieurs résultats Store ou RPC sont agrégés avec ambiguïtés | REDESSINER | Backfill snapshot | KSP doit distinguer entité, observation, purge, missing et conflit | `pre.007`, `pre.009` |
|
||||
| Prélecture Store avant hydratation | `hydrate_candidate`, branche `existing_skipped` | Une signature connue saute l'acquisition et l'observation | REJETER | Aucun | Course TOCTOU, purge et acquisition nouvelle perdues | Aucun |
|
||||
| Retry, pause et parsing texte | `retry_delay_ms`, `wait_with_cancellation`, branches HTTP 429 et timeout | Le Job rythme et retente selon des chaînes d'erreur | REJETER | Transport exclusivement | Amplification des retries et contrat d'erreur instable | Aucun |
|
||||
| Persistance missing ou failed | `persist_missing_observation`, `persist_failed_observation` | Une tentative sans transaction canonique devient observation durable | REPORTER | Futur contrat Store de journal d'acquisition | `RawTransactionObservation` KSP exige aujourd'hui une transaction complète | Version ultérieure |
|
||||
| Reprise durable multi-processus | Résumé `resume_before_signature` et application demo | Le caller reçoit une ancre, sans contrat de garde durable KSP | REPORTER | Application ou scheduler futur | Aucune propriété de persistance de checkpoint n'existe en v0.3.6 | Version ultérieure |
|
||||
|
||||
## 5. Ownership par couche
|
||||
|
||||
| Sujet | Propriétaire | Contrat |
|
||||
| --- | --- | --- |
|
||||
| Identité, état et annulation d'un Job | `ksp-job-api` | Types publics runtime-neutral et sans effet de bord |
|
||||
| Snapshot concret du backfill | `ksp-job-backfill-lib` | Phase, progression, checkpoint et résultat sûrs |
|
||||
| Pagination et hydratation Solana | `ksp-onchain-transport-lib` | Wrappers RPC typés existants |
|
||||
| Routage, pacing, retry et délai | `ksp-onchain-transport-lib` | Aucune boucle concurrente dans Job |
|
||||
| Conversion en RAW canonique | `ksp-job-backfill-lib` | Adaptateur concret du premier vertical |
|
||||
| Idempotence, conflit et purge | `ksp-store-lib` | Écriture atomique en mode `Normal` |
|
||||
| Logs internes | `ksp-logging-lib` | Champs sûrs, pas de secret ni réponse brute |
|
||||
| Configuration et supervision | Application v0.3.7 | Pas de dépendance Config en v0.3.6 |
|
||||
|
||||
## 6. Contrat public de `ksp-job-api`
|
||||
|
||||
La crate reste passive et ne dépend que de `ksp-core-lib` si un type fondamental existant est requis. Elle n'expose ni Tokio, ni Transport, ni Store, ni Logging, ni serde dans sa surface publique.
|
||||
|
||||
Types prévus :
|
||||
|
||||
- `JobId`, identifiant borné, validé et fourni par l'hôte afin de rester stable pendant une reprise ;
|
||||
- `JobKindCode`, code stable et borné sans texte humain libre ;
|
||||
- `JobState` : `Created`, `Running`, `Cancelling`, `Completed`, `Cancelled` ou `Failed` ;
|
||||
- `JobCompletion` : `Complete` ou `Partial`, le second signalant des trous attendus comme une transaction RPC absente ;
|
||||
- `JobNotificationSequence`, compteur strictement croissant vérifié ;
|
||||
- `JobNotification<S>`, enveloppe générique contenant l'identité, la séquence, l'état et le snapshot le plus récent ;
|
||||
- `JobSnapshotSource`, trait à type associé fournissant la valeur courante et une attente abstraite d'un changement après une séquence ;
|
||||
- `JobCancellationToken`, cloneable, idempotent et fondé sur des primitives `std`.
|
||||
|
||||
Le contrat ne contient pas de booléen redondant avec `JobState`, pas de message humain, pas de timestamp mural dont l'ownership serait ambigu et pas de méthode `spawn`.
|
||||
|
||||
La surface concrète du backfill accepte `BackfillRequest`, `HttpTransportPool` et un `Arc<Store>`. Sa construction rend un exécuteur possédé et un `BackfillJobHandle` cloneable ; l'hôte décide où exécuter la future `run`, tandis que le handle fournit annulation et observation. Les ports étroits employés par le moteur restent privés et servent aux doubles déterministes internes ; l'API publique ne duplique pas les contrats Transport ou Store.
|
||||
|
||||
## 7. Notifications latest-value multi-listeners
|
||||
|
||||
Audit des formes candidates avant choix :
|
||||
|
||||
| Forme | Runtime-neutralité API | Backpressure | Multi-listener | Livraison terminale | Resynchronisation | Testabilité | Réutilisation Worker future |
|
||||
| --- | --- | --- | --- | --- | --- | --- | --- |
|
||||
| Polling snapshot par caller | Forte | Naturellement bornée | Oui | Oui si le caller repolle | Snapshot direct | Simple mais latence artificielle | Possible, coûteux si polling serré |
|
||||
| Callback ou sink | Moyenne | Le producer peut bloquer | Composition manuelle | Fragile sur panic ou abandon | Aucune native | Courses et réentrance difficiles | Rejetée |
|
||||
| Watch ou latest-value | Forte avec futur abstrait | O(1), coalescence explicite | Oui | Oui, valeur retenue | Snapshot complet courant | Déterministe avec séquences | Bon motif, contrat Worker distinct plus tard |
|
||||
| Broadcast ou fan-out | Faible si primitive runtime publique | Lag ou pertes par consumer | Oui | Dépend du buffer et de l'abonnement | Besoin d'un snapshot parallèle | Sensible aux buffers | Ne pas imposer avant Worker API |
|
||||
| File bornée | Moyenne | Blocage ou drop à définir | Non sans fan-out supplémentaire | Peut être coincée derrière la progression | Replay limité au buffer | Nombreuses politiques périphériques | Trop lourde pour le besoin actuel |
|
||||
|
||||
Le choix est watch ou latest-value avec séquence et snapshot complet. Il conserve l'observabilité sans imposer la primitive d'un runtime à `ksp-job-api`.
|
||||
|
||||
Le modèle public est une valeur la plus récente, non une file d'événements :
|
||||
|
||||
- le producteur remplace un unique snapshot en mémoire en O(1) ;
|
||||
- chaque listener conserve sa dernière séquence et demande la valeur courante ou attend une séquence supérieure ;
|
||||
- un listener lent peut coalescer des progressions intermédiaires, mais reçoit toujours l'état courant complet ;
|
||||
- plusieurs listeners ne se bloquent pas mutuellement et n'exécutent jamais de code dans le producteur ;
|
||||
- l'état terminal reste lisible tant que le handle partagé existe ;
|
||||
- une séquence manquée provoque une resynchronisation par snapshot complet, pas une erreur de replay ;
|
||||
- l'échec d'un listener et son abandon n'affectent pas le Job ;
|
||||
- aucune donnée brute RPC, URL, secret, signature en masse ou erreur fournisseur libre n'est publique.
|
||||
|
||||
L'implémentation Tokio de réveil reste privée à `ksp-job-backfill-lib`. Le trait de l'API retourne un futur abstrait appartenant à la crate, sans type Tokio public.
|
||||
|
||||
## 8. Requête et portées du backfill
|
||||
|
||||
`BackfillRequest` exige tous les paramètres opérationnels ; la bibliothèque ne choisit pas silencieusement des valeurs d'application.
|
||||
|
||||
Champs communs :
|
||||
|
||||
- `JobId`, réseau Store attendu, rôle HTTP, engagement `Confirmed` ou `Finalized` ;
|
||||
- taille de page de 1 à 1 000 ;
|
||||
- nombre maximal de pages de 1 à 10 000 ;
|
||||
- limite maximale de 10 000 candidats par Job ;
|
||||
- concurrence d'hydratation de 1 à 64 ;
|
||||
- `min_context_slot` optionnel pour les portées adresse ;
|
||||
- scope et checkpoint optionnel validé contre l'identité du scope.
|
||||
|
||||
Portées :
|
||||
|
||||
- `LatestAddress` : adresse, sans ancre, retourne les signatures les plus récentes dans l'ordre RPC ;
|
||||
- `BeforeAddress` : adresse et ancre exclusive, remonte vers les signatures plus anciennes ;
|
||||
- `AfterAddress` : adresse et ancre exclusive, collecte la fenêtre plus récente la plus proche de l'ancre ;
|
||||
- `ExplicitSignatures` : liste non vide de signatures validées et bornées.
|
||||
|
||||
Le fingerprint de scope couvre réseau, adresse ou digest de liste, direction, ancre, engagement et limites sémantiques. Une reprise avec un checkpoint d'un autre scope est rejetée à l'admission.
|
||||
|
||||
## 9. Sémantique RPC officielle figée
|
||||
|
||||
| Appel | Configuration retenue | Sémantique consommée |
|
||||
| --- | --- | --- |
|
||||
| `getSignaturesForAddress` | engagement, `before`, `until`, `limit`, `minContextSlot` | Résultats du plus récent au plus ancien ; ancres exclusives |
|
||||
| `getTransaction` | engagement, encodage `base64`, version maximale `0` | Transaction confirmée ou `null`, avec slot, block time, transaction, meta et version |
|
||||
|
||||
Références officielles :
|
||||
|
||||
- <https://solana.com/docs/rpc/http/getsignaturesforaddress>
|
||||
- <https://solana.com/docs/rpc/http/gettransaction>
|
||||
|
||||
Décisions :
|
||||
|
||||
- `LatestAddress` et `BeforeAddress` suivent directement l'ordre de page officiel ;
|
||||
- `AfterAddress` utilise `until` pour rechercher l'ancre, conserve les candidats strictement plus récents et sélectionne les plus proches de l'ancre dans un ordre historique déterministe ;
|
||||
- une ancre `BeforeAddress` absente des pages n'est pas une erreur car elle sert de curseur RPC ;
|
||||
- une ancre `AfterAddress` non atteinte dans la borne de pages est un résultat partiel observable et ne produit aucun checkpoint au-delà du trou ;
|
||||
- les doublons inter-pages et intra-requête sont retirés à première occurrence sans réordonner arbitrairement ;
|
||||
- `getTransaction = null` devient `Missing`, sans écriture Store et sans fabrication de provenance.
|
||||
|
||||
## 10. Format RAW v1 et conversion
|
||||
|
||||
Le premier format concret est `ksp.solana.raw_transaction`, version `1`.
|
||||
|
||||
Acquisition :
|
||||
|
||||
- `getTransaction` est demandé en `base64` avec `maxSupportedTransactionVersion = 0` ;
|
||||
- les encodages `jsonParsed` et toute structure interprétée par un fournisseur sont refusés ;
|
||||
- la signature Base58 est décodée vers exactement 64 octets sans introduire le SDK Solana complet ;
|
||||
- un `blockTime` négatif, impossible à représenter par `RawTimestamp`, est une erreur de conversion terminale et n'est jamais supprimé silencieusement.
|
||||
|
||||
Payload canonique :
|
||||
|
||||
- UTF-8 JSON compact produit par un sérialiseur déterministe appartenant au backfill ;
|
||||
- jeu et ordre de clés figés par tests ;
|
||||
- transaction binaire conservée comme tuple base64 plus marqueur d'encodage ;
|
||||
- `meta`, `version` et `transactionIndex` conservent la distinction champ absent, `null` et valeur ;
|
||||
- le slot et le block time sont portés par `RawTransaction` et ne sont pas dupliqués dans les bytes ;
|
||||
- hash SHA-256 et taille sont calculés sur les bytes canoniques exacts avant construction de `RawPayload`.
|
||||
|
||||
Toute évolution de version de transaction supportée ou du format impose une nouvelle version de payload et des canaries de compatibilité.
|
||||
|
||||
## 11. Extension Transport minimale pour la provenance
|
||||
|
||||
Le wrapper typé actuel perd l'identité de l'endpoint finalement utilisé après routage ou retry. La v0.3.6 ajoute une voie additive observée pour `getTransaction` :
|
||||
|
||||
- elle réutilise exactement la même admission, la même sélection, les mêmes retries et le même décodage ;
|
||||
- elle retourne la valeur typée accompagnée d'un identifiant sûr de fournisseur et d'endpoint effectivement victorieux ;
|
||||
- elle n'expose ni URL, ni en-tête, ni body brut ;
|
||||
- l'API existante reste compatible et peut continuer à ne retourner que la valeur ;
|
||||
- les tests prouvent qu'un reroutage rapporte l'endpoint final, pas le candidat initial.
|
||||
|
||||
Cette extension ne crée pas un second client RPC et ne déplace aucune politique vers Job.
|
||||
|
||||
## 12. Provenance, observation et identité
|
||||
|
||||
Chaque transaction disponible produit :
|
||||
|
||||
- une `RawTransactionReference` composée du réseau et de la signature 64 octets ;
|
||||
- une `RawTransaction` complète et canonique ;
|
||||
- une `RawAcquisitionProvenance` avec fournisseur, protocole Solana HTTP JSON-RPC, méthode `getTransaction`, endpoint sûr, engagement, instant de réception et `JobId` comme session de capture ;
|
||||
- une `RawTransactionObservation` dont la clé déterministe est un SHA-256 domain-separated du `JobId`, du fingerprint de scope, de la signature, du fournisseur, de l'endpoint, de l'engagement et de la version du contrat d'acquisition.
|
||||
|
||||
Une reprise du même Job par le même endpoint retrouve donc l'observation. Un autre endpoint constitue une nouvelle acquisition légitime. Une différence de payload canonique pour la même référence reste un conflit Store, jamais un skip.
|
||||
|
||||
## 13. Persistance et idempotence
|
||||
|
||||
Le Job appelle uniquement `ksp-store-lib::persist_raw_transaction_acquisition` en mode `Normal`.
|
||||
|
||||
Sont distingués dans les compteurs :
|
||||
|
||||
- entité `Inserted`, `AlreadyPresent` ou `SkippedPurged` ;
|
||||
- observation `Inserted`, `AlreadyPresent` ou `NotRecorded` ;
|
||||
- transaction RPC `Missing` ;
|
||||
- conflit, conversion invalide, erreur Transport et erreur Store.
|
||||
|
||||
Il n'existe pas de prélecture `has` avant hydratation. Une relance avec le même `JobId` et le même scope est logiquement idempotente : l'entité n'est pas dupliquée, l'observation identique est déjà présente et les différences réelles restent visibles.
|
||||
|
||||
Les tentatives sans transaction canonique restent dans le résultat runtime. Leur journalisation persistante est reportée à un contrat Store ultérieur explicite.
|
||||
|
||||
## 14. Ordonnancement, bornes et backpressure
|
||||
|
||||
Le Job sépare trois étapes : découverte, hydratation, persistance.
|
||||
|
||||
- découverte paginée séquentielle afin de préserver les ancres et l'ordre ;
|
||||
- collection bornée à 10 000 candidats et déduplication stable ;
|
||||
- au plus 64 hydratations en vol, limitées par la requête ;
|
||||
- une persistance par candidat hydraté, sans file non bornée ;
|
||||
- admissions arrêtées dès annulation ou erreur fatale ;
|
||||
- fin des opérations Store déjà soumises avant publication de l'état terminal afin de ne jamais ignorer un commit possible ;
|
||||
- compteurs `u64` mis à jour par opérations vérifiées, un overflow devenant une violation d'invariant sûre et non un panic.
|
||||
|
||||
Le débit externe reste gouverné par le pool Transport. Ajouter une temporisation Job serait une amplification de politique interdite.
|
||||
|
||||
## 15. Frontière contiguë et checkpoint
|
||||
|
||||
Chaque candidat reçoit un index stable après déduplication. Le checkpoint ne progresse que sur le préfixe contigu des résultats durables suivants :
|
||||
|
||||
- entité insérée ;
|
||||
- entité déjà présente avec contenu identique ;
|
||||
- entité purgée et correctement sautée par la politique Store normale.
|
||||
|
||||
`Missing`, conflit, erreur de conversion, erreur Transport, erreur Store ou annulation créent un trou. Des candidats ultérieurs peuvent terminer, mais la frontière ne saute jamais ce trou.
|
||||
|
||||
Le checkpoint contient `JobId`, fingerprint de scope, index de frontière et curseur RPC nécessaire. Il ne contient pas de payload ni de secret. Pour `LatestAddress`, une reprise repart volontairement de la vue latest et s'appuie sur l'idempotence Store ; pour `BeforeAddress`, elle reprend au dernier curseur contigu ; pour `AfterAddress` et `ExplicitSignatures`, elle rejoue le scope borné et ignore uniquement le préfixe validé par le checkpoint.
|
||||
|
||||
## 16. Annulation et courses terminales
|
||||
|
||||
Annuler est idempotent et fait passer `Running` vers `Cancelling` dès que le producteur observe le token.
|
||||
|
||||
| Situation | Décision |
|
||||
| --- | --- |
|
||||
| Attente ou page RPC en cours | Le futur Transport est abandonné par sélection coopérative |
|
||||
| Hydratation RPC longue | Le futur est abandonné ; aucune persistance n'est fabriquée |
|
||||
| Candidat pas encore admis | Il ne démarre pas |
|
||||
| Persistance Store déjà soumise | Elle est attendue et son résultat est compté |
|
||||
| Dernier travail finit avant observation du cancel | `Completed` gagne si tout est durable |
|
||||
| Cancel observé avec travail restant | `Cancelled` gagne après drainage sûr |
|
||||
| Erreur fatale et cancel simultanés | L'erreur déjà observée reste la cause ; le snapshot indique l'arrêt des admissions |
|
||||
|
||||
Le token ne promet pas l'interruption d'un commit externe. La garantie est l'absence de nouvelle admission et une terminaison avec état connu.
|
||||
|
||||
## 17. États, phases, résultats, logging et sécurité
|
||||
|
||||
`BackfillSnapshot` est complet et borné. Il contient :
|
||||
|
||||
- phase `Discovering`, `Hydrating`, `Persisting` ou `Draining` ;
|
||||
- scope sûr réduit à son type, son fingerprint et ses bornes ;
|
||||
- pages lues, doublons supprimés, candidats sélectionnés, démarrés et terminés ;
|
||||
- hydratés, manquants, insérés, déjà présents et purgés ;
|
||||
- observations insérées, déjà présentes et non enregistrées ;
|
||||
- conflits et échecs classés par code stable ;
|
||||
- concurrence en vol et checkpoint courant ;
|
||||
- résultat final `Complete`, `Partial`, `Cancelled` ou `Failed`.
|
||||
|
||||
Une transaction `Missing` produit `Completed` avec résultat `Partial` si aucune erreur fatale ne survient. Un conflit, une conversion invalide ou une erreur d'infrastructure après les politiques Transport arrête les admissions et produit `Failed`.
|
||||
|
||||
Transitions autorisées :
|
||||
|
||||
- `Created` vers `Running` ou `Cancelled` si l'annulation précède le départ ;
|
||||
- `Running` vers `Cancelling`, `Completed` ou `Failed` ;
|
||||
- `Cancelling` vers `Cancelled`, `Completed` si la course est déjà entièrement durable, ou `Failed` si une erreur déjà observée gagne ;
|
||||
- aucun retour vers un état antérieur et une seule publication terminale immuable.
|
||||
|
||||
Logging et sécurité :
|
||||
|
||||
- target constant propre à la crate selon les conventions Logging existantes, sans nouveau document `std.jobs.*` ;
|
||||
- champs sûrs : JobId, fingerprint de scope, phase, codes, bornes et compteurs ;
|
||||
- pas d'URL, credential, body RPC, payload RAW, SQL, liste de signatures ou texte fournisseur ;
|
||||
- `Debug` des types publics borné et redacted, champs privés et enums évolutifs `#[non_exhaustive]` lorsque pertinent ;
|
||||
- erreurs publiques structurées par `ErrorCode`, sans état stringly-typed.
|
||||
|
||||
## 18. Politique de retries
|
||||
|
||||
| Opération | Propriétaire du retry | Politique Job |
|
||||
| --- | --- | --- |
|
||||
| `getSignaturesForAddress` | Transport | Aucun retry supplémentaire |
|
||||
| `getTransaction` | Transport | Aucun retry supplémentaire |
|
||||
| Écriture Store | Store ou backend selon son contrat | Aucun retry aveugle |
|
||||
| Job complet | Hôte futur | Nouvelle exécution explicite avec même JobId ou nouveau JobId |
|
||||
|
||||
Les erreurs typées finales du Transport deviennent des codes de résultat Job. Les chaînes de fournisseurs ne sont jamais analysées.
|
||||
|
||||
## 19. Dépendances et firewalls
|
||||
|
||||
Graphe prévu :
|
||||
|
||||
- `ksp-job-api` vers Core uniquement si nécessaire ;
|
||||
- `ksp-job-backfill-lib` vers `ksp-job-api`, Core, Logging, Onchain Transport et `ksp-store-lib` ;
|
||||
- Tokio, futures, SHA-256, JSON canonique et un décodeur Base58 minimal restent des détails privés du backfill ;
|
||||
- aucune dépendance vers `ksp-store-api`, `ksp-store-postgres-lib`, App, Config, Wallet, Program API ou Interface ;
|
||||
- aucun retour inverse de Core, Transport, Store ou Logging vers Job ;
|
||||
- aucun type Tokio, reqwest, deadpool, PostgreSQL ou fournisseur dans l'API publique.
|
||||
|
||||
Aucun document Config `std.jobs.*` n'est introduit. Les réglages de Job sont fournis explicitement par l'hôte ; la composition Config appartient à l'application v0.3.7.
|
||||
|
||||
La dépendance Base58 exacte sera ajoutée seulement après audit de version et de features lors de la tranche de conversion.
|
||||
|
||||
## 20. Stratégie de tests et canaries
|
||||
|
||||
Tests de `ksp-job-api` :
|
||||
|
||||
- validation et bornes des identifiants ;
|
||||
- transitions autorisées et états terminaux immuables ;
|
||||
- annulation cloneable et idempotente ;
|
||||
- séquence strictement monotone, resynchronisation et conservation terminale ;
|
||||
- listener lent, plusieurs listeners et listener abandonné sans blocage producteur.
|
||||
|
||||
Tests de `ksp-job-backfill-lib` :
|
||||
|
||||
- validation de chaque scope et de toutes les bornes ;
|
||||
- pagination latest, before et after selon fixtures officielles ;
|
||||
- ancre after non atteinte, doublons inter-pages et ordre déterministe ;
|
||||
- signatures explicites dédupliquées à première occurrence ;
|
||||
- conversion Base58 exacte, RAW v1 déterministe, absent contre `null` et block time négatif ;
|
||||
- provenance de l'endpoint réellement victorieux ;
|
||||
- Store `Inserted`, `AlreadyPresent`, `SkippedPurged`, observation déjà présente, conflit et missing ;
|
||||
- relance idempotente avec même JobId et nouvelle observation avec endpoint différent ;
|
||||
- concurrence bornée, aucune file croissante et front contigu malgré fin désordonnée ;
|
||||
- annulation pendant découverte, hydratation, attente Transport et persistance ;
|
||||
- course annulation contre complétion et checkpoint sans saut de trou.
|
||||
|
||||
Canaries externes :
|
||||
|
||||
- Job API depuis une crate externe, sans construction de champs privés ;
|
||||
- Backfill depuis une crate externe avec ses types de production opaques ; doubles des ports privés uniquement dans les tests internes ;
|
||||
- interdiction de dépendance et de type public ;
|
||||
- smoke opt-in Devnet plus PostgreSQL configuré, sans endpoint payant ni secret versionné.
|
||||
|
||||
## 21. Découpage prévisionnel
|
||||
|
||||
Chaque tranche vise 15 à 20 minutes et doit produire une preuve atomique.
|
||||
|
||||
| Préversion | Critère d'entrée | Contenu atomique | Critère de sortie |
|
||||
| --- | --- | --- | --- |
|
||||
| `pre.001` | Stable v0.3.5 vérifiée | Audit, décisions, plan et validation | Documents, delta et bump de version uniquement |
|
||||
| `pre.002` | Plan 027 accepté | Identité, lifecycle et token d'annulation Job API | Unitaires API et audit firewalls |
|
||||
| `pre.003` | Transitions Job stables | Notifications latest-value génériques | Tests multi-listeners et canary externe API |
|
||||
| `pre.004` | Besoin provenance confirmé | Retour Transport observé pour `getTransaction` | Tests routage, retry et endpoint victorieux |
|
||||
| `pre.005` | Transport observé stable | Scaffold backfill, requêtes, scopes et découverte | Fixtures latest, before, after et explicite |
|
||||
| `pre.006` | Candidats déterministes | Conversion RAW v1 et provenance | Golden bytes, hash, Base58 et absent ou null |
|
||||
| `pre.007` | RAW v1 figé | Persistance Store et idempotence | Insert, already, purge, observation, missing et conflit |
|
||||
| `pre.008` | Résultats Store typés | Concurrence, résultats et frontière | Fin désordonnée, bornes et checkpoint |
|
||||
| `pre.009` | Frontière prouvée | Annulation et snapshots concrets | Courses terminales et listener lent |
|
||||
| `pre.010` | Vertical complet | Hardening, canaries externes et firewalls | Tests adversariaux et preuve live si disponible |
|
||||
| `pre.011` | Aucun défaut fonctionnel ouvert | Gate technique final et smoke opt-in | Workspace vert et cargo trees audités |
|
||||
| `pre.012` | Gate technique vert | Réconciliation documentaire finale | Architecture, index, roadmap et contrats alignés |
|
||||
| `pre.013` | Documentation réconciliée | Gate de publication | Changelog, prompt v0.3.7 et archive minimale |
|
||||
| `rel.001` | Gate de publication vert | Publication stable | Version stable et deltas Git séparés |
|
||||
|
||||
Le smoke live reste conditionnel à un PostgreSQL explicitement configuré. Son absence n'annule pas la preuve déterministe et ne doit pas conduire à inventer un résultat.
|
||||
|
||||
## 22. Critères de fermeture
|
||||
|
||||
La v0.3.6 est fermable lorsque :
|
||||
|
||||
- les deux crates canoniques existent et respectent les firewalls ;
|
||||
- Job API reste passive, runtime-neutral et sans dépendance de domaine ;
|
||||
- l'observation latest-value est non bloquante, multi-listeners et terminalement persistante ;
|
||||
- les quatre scopes sont bornés, ordonnés, dédupliqués et couverts ;
|
||||
- le wrapper Transport observé prouve la provenance réelle sans dupliquer sa politique ;
|
||||
- le format RAW v1 est déterministe et ses canaries couvrent toutes les variantes wire ;
|
||||
- la persistance passe uniquement par `ksp-store-lib` en mode normal ;
|
||||
- insertion, déjà présent, purge, observation, missing et conflit restent distinguables ;
|
||||
- la frontière ne saute aucun trou sous concurrence ou annulation ;
|
||||
- une relance logique est idempotente ;
|
||||
- unitaires, canaries externes, audits de règles, tables, `cargo check`, `cargo clippy` et `cargo test` sont verts dans un environnement Rust disponible ;
|
||||
- les documents d'architecture utilisent `ksp-job-backfill-lib` et ne promettent aucun Worker ou pipeline partagé prématuré ;
|
||||
- la publication stable suit les lanes documentaires et Git séparées de `VERSION_WORKFLOW.md`.
|
||||
188
docs/validation/023-V0_3_6_JOB_API_BACKFILL.md
Normal file
188
docs/validation/023-V0_3_6_JOB_API_BACKFILL.md
Normal file
@@ -0,0 +1,188 @@
|
||||
<!-- file: docs/validation/023-V0_3_6_JOB_API_BACKFILL.md -->
|
||||
<!-- version: 1 -->
|
||||
|
||||
# Validation v0.3.6 — Job API et premier backfill RAW
|
||||
|
||||
## 1. Statut de `pre.001`
|
||||
|
||||
- [X] Archive v0.3.5 testée intégralement avant extraction.
|
||||
- [X] Archive kbot3 testée intégralement et isolée de l'arbre source KSP.
|
||||
- [X] Noms d'entrées contrôlés contre chemins absolus, traversées et liens symboliques ZIP.
|
||||
- [X] Règles canoniques, architecture, roadmap, changelog et deltas antérieurs lus.
|
||||
- [X] Surfaces Core, Transport et Store réelles auditées.
|
||||
- [X] Documentation officielle Solana des deux appels RPC auditée.
|
||||
- [X] kbot3 utilisé uniquement comme référence fonctionnelle et corpus de scénarios.
|
||||
- [X] Matrice reprendre, redessiner, reporter ou rejeter consignée.
|
||||
- [X] Ownership, limites, notifications, retry, annulation, provenance et checkpoint décidés.
|
||||
- [X] Plan découpé en tranches atomiques de 15 à 20 minutes.
|
||||
- [X] Aucun code de crate, manifeste de crate ou test Rust généré en `pre.001`.
|
||||
- [X] Version workspace ouverte en `0.3.6-pre.1`.
|
||||
|
||||
## 2. Preuve des archives d'entrée
|
||||
|
||||
| Archive | SHA-256 | Résultat ZIP | Usage |
|
||||
| --- | --- | --- | --- |
|
||||
| `khadhroony-solana-project-v0.3.5-thelatest1.zip` | `c395efb6a09c786069aa5063a7a414b55f8ae24e2b9d6eaa414b2f312cd62588` | 1 515 entrées, test intégral propre | Source exclusive de code |
|
||||
| `khadhroony-bot3_v0.5.3-pre.005-fix010-thelatest1.zip` | `ee47643b9f8b582ee8db97b2381ec107e45aef8c009fee44757531514615d318` | 2 501 entrées, test intégral propre | Référence fonctionnelle isolée |
|
||||
| `Texte collé.txt` | `0b86c7107349bcbf0a80319ba8cf9aabdd2809f27af9459592667f3e425e8d58` | Sans objet | Journal opérateur v0.3.5 |
|
||||
|
||||
Aucune entrée absolue, traversée, avec séparateur inversé ou lien symbolique ZIP n'a été trouvée dans les deux archives.
|
||||
|
||||
## 3. Baseline v0.3.5 observée
|
||||
|
||||
- [X] `scripts/audit_rust_workspace_rules.py` : propre avant modification.
|
||||
- [X] `scripts/audit_markdown_tables.py` : 240 tables et 150 fichiers propres avant modification.
|
||||
- [X] Audits Rust workspace post-modification : propres, aucune crate Rust ajoutée.
|
||||
- [X] Audit Markdown post-modification complet hors deltas historiques antérieurs : 240 tables et 153 fichiers propres.
|
||||
- [X] Contrats et tailles des trois nouveaux fichiers vérifiés.
|
||||
- [X] Le journal opérateur fourni rapporte `cargo fmt`, audits, `cargo check`, `cargo clippy` et suites Interface ou Program verts sur v0.3.5.
|
||||
- [X] L'environnement courant ne fournit pas l'exécutable `cargo` ; aucune validation Rust locale n'est annoncée comme exécutée.
|
||||
- [X] Le workspace compte 14 crates et aucune crate Job au point de départ.
|
||||
|
||||
## 4. Autorités et cohérence documentaire
|
||||
|
||||
- [X] `RULES.md` et la hiérarchie documentaire ont été appliqués.
|
||||
- [X] Les chemins canoniques réels sous `docs/rules/` ont été retenus lorsque le prompt utilisait un chemin abrégé.
|
||||
- [X] Le nom fixé est `ksp-job-backfill-lib`.
|
||||
- [X] Les occurrences historiques de `ksp-job-backfill` sont identifiées pour réconciliation avant fermeture.
|
||||
- [X] `ksp-job-api` et `ksp-job-backfill-lib` sont obligatoires en parallèle dans la release finale.
|
||||
- [X] Worker API, application et pipeline RAW partagé sont explicitement hors v0.3.6.
|
||||
- [ ] Architecture, README, roadmap et contrats finaux réconciliés avant publication.
|
||||
|
||||
## 5. Contrat `ksp-job-api`
|
||||
|
||||
- [ ] `JobId` borné, validé et opaque.
|
||||
- [ ] `JobKindCode` stable et sans texte humain libre.
|
||||
- [ ] Transitions `Created`, `Running`, `Cancelling`, `Completed`, `Cancelled` et `Failed` contrôlées.
|
||||
- [ ] États terminaux immuables et résultat complet ou partiel explicite.
|
||||
- [ ] `JobCancellationToken` cloneable, idempotent et sans Tokio public.
|
||||
- [ ] `JobNotificationSequence` strictement monotone avec overflow traité sans panic.
|
||||
- [ ] `JobNotification<S>` contient un snapshot complet, sûr et borné.
|
||||
- [ ] `JobSnapshotSource` permet lecture courante et attente de changement sans callback producteur.
|
||||
- [ ] Aucun type Transport, Store, Logging, Tokio ou serde dans l'API publique.
|
||||
- [ ] Canaries externes de construction, observation et annulation vertes.
|
||||
|
||||
## 6. Notifications multi-listeners
|
||||
|
||||
- [ ] Stockage O(1) de la dernière valeur, sans historique non borné.
|
||||
- [ ] Un listener lent ne bloque jamais le producteur.
|
||||
- [ ] Plusieurs listeners progressent indépendamment.
|
||||
- [ ] Une séquence manquée se résout par resynchronisation du snapshot courant.
|
||||
- [ ] L'abandon ou l'échec d'un listener n'affecte pas le Job.
|
||||
- [ ] Le snapshot terminal reste disponible tant que le handle existe.
|
||||
- [ ] Aucun secret, URL, body RPC, liste massive ou message fournisseur n'est exposé.
|
||||
|
||||
## 7. Admission et portées
|
||||
|
||||
- [ ] `LatestAddress`, `BeforeAddress`, `AfterAddress` et `ExplicitSignatures` couverts.
|
||||
- [ ] Adresse validée par le type Core existant.
|
||||
- [ ] Signature Base58 décodée vers exactement 64 octets.
|
||||
- [ ] Engagement limité à `Confirmed` ou `Finalized`.
|
||||
- [ ] Taille de page entre 1 et 1 000.
|
||||
- [ ] Nombre de pages entre 1 et 10 000.
|
||||
- [ ] Nombre de candidats entre 1 et 10 000.
|
||||
- [ ] Concurrence d'hydratation entre 1 et 64.
|
||||
- [ ] `min_context_slot` transmis sans en changer le sens.
|
||||
- [ ] Checkpoint rejeté si son JobId ou son fingerprint de scope diffère.
|
||||
- [ ] Aucun défaut d'application silencieux dans la bibliothèque.
|
||||
|
||||
## 8. Pagination, ordre et déduplication
|
||||
|
||||
- [ ] Latest respecte l'ordre officiel du plus récent au plus ancien.
|
||||
- [ ] Before utilise une ancre exclusive et avance vers l'historique plus ancien.
|
||||
- [ ] After atteint l'ancre, garde les plus proches candidats plus récents et produit un ordre déterministe.
|
||||
- [ ] Ancre before absente acceptée comme curseur.
|
||||
- [ ] Ancre after non atteinte bornée et signalée comme résultat partiel.
|
||||
- [ ] Doublons inter-pages retirés à première occurrence.
|
||||
- [ ] Doublons explicites retirés sans réordonner la liste.
|
||||
- [ ] Limites de pages et candidats respectées sur tous les chemins.
|
||||
|
||||
## 9. Acquisition Transport et retry
|
||||
|
||||
- [ ] Les wrappers typés officiels restent l'unique chemin RPC.
|
||||
- [ ] Retour observé additif de `getTransaction` couvert.
|
||||
- [ ] Fournisseur et endpoint rapportés correspondent au succès réel après reroutage.
|
||||
- [ ] URL, headers et body brut restent privés.
|
||||
- [ ] Aucun retry, pacing ou sélection d'endpoint dans Job.
|
||||
- [ ] Aucune détection d'erreur par chaîne.
|
||||
- [ ] Une erreur Transport finale arrête les admissions avec un code Job sûr.
|
||||
|
||||
## 10. RAW v1 et provenance
|
||||
|
||||
- [ ] Format `ksp.solana.raw_transaction`, version `1`, figé.
|
||||
- [ ] `getTransaction` demandé en `base64` avec version maximale `0`.
|
||||
- [ ] Bytes JSON compacts déterministes et golden tests stables.
|
||||
- [ ] Transaction binaire conservée sans décodage métier.
|
||||
- [ ] `meta`, `version` et `transactionIndex` distinguent absent, `null` et valeur.
|
||||
- [ ] Slot et block time non dupliqués dans le payload.
|
||||
- [ ] Block time négatif rejeté explicitement.
|
||||
- [ ] Hash SHA-256 et taille calculés sur les bytes exacts.
|
||||
- [ ] Provenance contient fournisseur, protocole, méthode, endpoint sûr, engagement et réception.
|
||||
- [ ] Clé d'observation domain-separated et déterministe.
|
||||
- [ ] Endpoint différent produit une nouvelle observation légitime.
|
||||
|
||||
## 11. Persistance Store
|
||||
|
||||
- [ ] Dépendance unique vers `ksp-store-lib`, jamais vers backend ou API directement.
|
||||
- [ ] Écriture atomique transaction plus observation en mode `Normal`.
|
||||
- [ ] Aucune prélecture de présence avant hydratation.
|
||||
- [ ] Entités insérée, déjà présente et purgée distinguées.
|
||||
- [ ] Observations insérée, déjà présente et non enregistrée distinguées.
|
||||
- [ ] `getTransaction = null` ne produit aucune écriture fabriquée.
|
||||
- [ ] Conflit Store reste une erreur terminale visible.
|
||||
- [ ] Même JobId et même endpoint donnent une relance idempotente.
|
||||
- [ ] `ForceRehydrate` inaccessible depuis le chemin normal.
|
||||
|
||||
## 12. Concurrence, frontière et checkpoint
|
||||
|
||||
- [ ] Nombre d'hydratations en vol borné par la requête.
|
||||
- [ ] Aucune file de candidats ou notifications non bornée.
|
||||
- [ ] Résultats hors ordre réconciliés par index stable.
|
||||
- [ ] Frontière avance sur insert, already present et purged skip durables.
|
||||
- [ ] Missing, conflit, conversion, Transport, Store et annulation créent un trou.
|
||||
- [ ] Frontière ne saute jamais un trou même si des résultats ultérieurs terminent.
|
||||
- [ ] Latest rejoue la vue courante de façon idempotente.
|
||||
- [ ] Before reprend au curseur contigu.
|
||||
- [ ] After et explicite rejouent le scope borné avec préfixe validé.
|
||||
|
||||
## 13. Annulation et résultats terminaux
|
||||
|
||||
- [ ] Annulation idempotente et état `Cancelling` observable.
|
||||
- [ ] Admissions arrêtées après observation du token.
|
||||
- [ ] Attente de page et hydratation RPC longues abandonnables.
|
||||
- [ ] Persistance déjà soumise drainée jusqu'à un résultat connu.
|
||||
- [ ] Course complétion contre annulation déterministe.
|
||||
- [ ] Course erreur fatale contre annulation déterministe.
|
||||
- [ ] Missing seul donne `Completed` avec résultat `Partial`.
|
||||
- [ ] Conflit, conversion ou infrastructure donnent `Failed`.
|
||||
- [ ] Checkpoint final cohérent avec le dernier préfixe durable.
|
||||
|
||||
## 14. Firewalls et qualité Rust
|
||||
|
||||
- [ ] `ksp-job-api` ne dépend au plus que de Core.
|
||||
- [ ] `ksp-job-backfill-lib` ne dépend pas de Config, App, Wallet, Program, Interface ou backend Store.
|
||||
- [ ] Aucun retour de dépendance vers Job depuis les couches basses.
|
||||
- [ ] Versions et features des nouvelles dépendances auditées avant ajout.
|
||||
- [ ] `cargo fmt --all -- --check` vert.
|
||||
- [ ] `scripts/audit_rust_workspace_rules.py` vert.
|
||||
- [ ] `scripts/audit_markdown_tables.py` vert.
|
||||
- [X] Contrats de fichiers de `FILE_CONTRACTS.md` vérifiés sur chaque ajout de `pre.001`.
|
||||
- [ ] `cargo check --workspace` vert.
|
||||
- [ ] `cargo clippy --workspace --all-targets --all-features -- -D warnings` vert.
|
||||
- [ ] `cargo test --workspace --all-targets --all-features` vert.
|
||||
- [ ] `cargo tree -d` examiné et doublons justifiés.
|
||||
|
||||
## 15. Preuves d'intégration et fermeture
|
||||
|
||||
- [ ] Fake Transport et fake Store déterministes sans backend direct.
|
||||
- [ ] Vertical découverte, hydratation, conversion, persistance et observation couvert.
|
||||
- [ ] Smoke Devnet plus PostgreSQL configuré exécuté si l'environnement explicite est disponible.
|
||||
- [ ] Aucun endpoint payant, credential ou donnée sensible requis par les tests normaux.
|
||||
- [ ] Gates technique, documentaire et de publication séparés.
|
||||
- [ ] CHANGELOG et ROADMAP alignés seulement après preuve technique.
|
||||
- [ ] Archives de release minimales et vérifiées.
|
||||
- [ ] Version stable publiée uniquement après tous les critères obligatoires.
|
||||
|
||||
## 16. Règle de vérité
|
||||
|
||||
Une case n'est cochée que par une preuve effectivement exécutée ou un audit effectivement réalisé. L'absence de `cargo`, de PostgreSQL configuré ou d'accès live est rapportée comme non exécutée ; elle n'est jamais convertie en succès implicite.
|
||||
Reference in New Issue
Block a user