v0.2.7-pre.014

This commit is contained in:
2026-08-23 11:15:57 +02:00
parent 3c5786f273
commit 5aa7b45840
23 changed files with 1605 additions and 103 deletions

View File

@@ -0,0 +1,135 @@
<!-- file: crates/ksp-core-lib/README.md -->
<!-- version: 1 -->
# `ksp-core-lib`
`ksp-core-lib` porte les contrats fondamentaux partagés par les couches KSP sans dépendre des domaines de plus haut niveau.
La crate possède actuellement trois responsabilités :
- le type d'erreur commun KSP ;
- le type Solana `Pubkey` réexporté comme primitive d'adresse canonique ;
- le registre KSP des Program IDs Solana fondamentaux et leur taxonomie.
## Frontière architecturale
Core reste une couche basse. Elle ne possède ni configuration, ni logging runtime, ni transport réseau, ni wallet, ni stockage, ni logique de décodage/exécution.
Les crates de niveau supérieur peuvent dépendre de Core et réutiliser ses contrats ; Core ne doit pas introduire de dépendance inverse vers ces couches.
La seule dépendance runtime externe directe actuelle est `solana-pubkey`, utilisée pour la primitive `Pubkey`.
## Erreur commune KSP
Le contrat d'erreur public repose sur :
```text
ErrorCode
ErrorContext
Error
Result<T>
```
`ErrorCode` sépare un `domain` stable d'un `code` stable. `Error` ajoute :
- un message humain ;
- des champs de contexte ordonnés ;
- une source d'erreur standard optionnelle compatible `Send + Sync`.
L'affichage d'une erreur reste compact :
```text
<domain>.<code>: <message>
```
Les consommateurs ajoutent uniquement des contextes sûrs. Les secrets, credentials, key material, URLs sensibles ou payloads massifs ne doivent pas être copiés dans le message ou le contexte d'une erreur.
## `Pubkey`
La crate réexporte :
```rust
ksp_core_lib::Pubkey
```
Les autres crates KSP utilisent cette primitive lorsqu'un contrat public a besoin d'une adresse Solana générique. Elles évitent ainsi de multiplier les propriétaires de type pour la même notion.
## Program IDs fondamentaux
Core possède 18 Program IDs Solana fondamentaux sous deux formes cohérentes :
```text
PRGID_* -> Base58 &str
PRGIDPK_* -> Pubkey typé
```
Les deux formes sont déclarées depuis une même valeur grâce à `declare_program_id!`.
Le registre couvre notamment :
- System, Vote, Stake, Config et Feature ;
- Address Lookup Table et Compute Budget ;
- les loaders natif, BPF v1, BPF v2, BPF upgradeable et Loader v4 ;
- les precompiles Ed25519, Secp256k1 et Secp256r1 ;
- Slashing ;
- ZK ElGamal Proof et ZK Token Proof.
Le registre n'est pas un catalogue général de tous les programmes Solana. Les protocoles, applications et Program IDs de domaines futurs sont ajoutés dans les couches propriétaires appropriées lorsqu'un besoin réel existe.
## Registre et taxonomie
Chaque entrée est exposée sous `ProgramIdEntry` avec :
```text
code
name
program_id
pubkey
domain
family
protocol
subfamily
program_version
kind
```
`ProgramIdKind` distingue actuellement :
```text
Program
Loader
Precompile
EnshrinedProgram
```
La lecture du registre s'effectue par :
```text
entries()
native_program_ids()
program_ids(filter)
program_ids_by_domain(...)
program_ids_by_family(...)
program_ids_by_protocol(...)
find_program_id(...)
find_program_pubkey(...)
```
`ProgramIdFilter` permet de combiner les axes `domain`, `family`, `protocol`, `subfamily`, `program_version` et `kind`.
## Garanties validées
Les tests publics et unitaires verrouillent notamment :
- la correspondance Base58 / `Pubkey` des constantes ;
- l'unicité des codes, Program IDs et pubkeys du registre ;
- les 18 entrées fondamentales ;
- les recherches textuelles et typées ;
- les vues et filtres taxonomiques ;
- l'absence des comptes well-known qui ne sont pas des programmes ;
- le contrat `Error`, son ordre de contexte et sa chaîne `source()` ;
- `Error: Send + Sync`.
## Documentation
- [`USAGE.md`](USAGE.md) — exemples d'utilisation de l'erreur commune, de `Pubkey`, des Program IDs et du registre.

View File

@@ -0,0 +1,265 @@
<!-- file: crates/ksp-core-lib/USAGE.md -->
<!-- version: 1 -->
# Utilisation de `ksp-core-lib`
## Utiliser le type d'erreur commun
Une crate KSP définit des codes stables puis retourne `ksp_core_lib::Result<T>` :
```rust
const ERROR_CODE_LOAD_FAILED: ksp_core_lib::ErrorCode =
ksp_core_lib::ErrorCode::new("store", "load_failed");
fn load_value() -> ksp_core_lib::Result<u64> {
let error = ksp_core_lib::Error::new(
ERROR_CODE_LOAD_FAILED,
"unable to load value",
)
.with_context("operation", "load_value");
return std::result::Result::Err(error);
}
```
Le domaine et le code sont accessibles séparément :
```rust
let code = ERROR_CODE_LOAD_FAILED;
assert_eq!(code.domain(), "store");
assert_eq!(code.code(), "load_failed");
```
Le message et les champs de contexte restent accessibles sans parser `Display` :
```rust
let error = ksp_core_lib::Error::new(
ERROR_CODE_LOAD_FAILED,
"unable to load value",
)
.with_context("component", "postgres")
.with_context("operation", "load_value");
assert_eq!(error.code(), ERROR_CODE_LOAD_FAILED);
assert_eq!(error.message(), "unable to load value");
assert_eq!(error.context()[0].key(), "component");
assert_eq!(error.context()[0].value(), "postgres");
```
Un `ErrorContext` peut aussi être construit indépendamment lorsquun caller prépare explicitement son contexte :
```rust
let context = ksp_core_lib::ErrorContext::new(
"operation",
"load_value",
);
assert_eq!(context.key(), "operation");
assert_eq!(context.value(), "load_value");
```
Les valeurs de contexte doivent être sûres à exposer. Ne pas y placer de secret, credential, seed, key material, URL contenant une clé API ou payload brut volumineux.
## Conserver une erreur source
Une erreur externe compatible `Send + Sync + 'static` peut rester dans la chaîne standard :
```rust
#[derive(Debug)]
struct SourceError;
impl std::fmt::Display for SourceError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
return formatter.write_str("source failure");
}
}
impl std::error::Error for SourceError {}
let error = ksp_core_lib::Error::new(
ERROR_CODE_LOAD_FAILED,
"unable to load value",
)
.with_source(SourceError);
let source = std::error::Error::source(&error);
assert!(source.is_some());
```
La source sert au chaînage d'erreurs ; son contenu ne doit pas être recopié sans contrôle dans un contexte ou un log public.
## Utiliser `Pubkey`
Core réexporte la primitive Solana utilisée par les contrats KSP :
```rust
let system = ksp_core_lib::Pubkey::from_str_const(
ksp_core_lib::PRGID_SOLANA_SYSTEM,
);
assert_eq!(system, ksp_core_lib::PRGIDPK_SOLANA_SYSTEM);
```
Une crate consommatrice peut donc utiliser `ksp_core_lib::Pubkey` dans sa propre API sans dépendre directement de `solana-pubkey` lorsque Core est déjà le propriétaire architectural de cette primitive.
## Utiliser les constantes Program ID
Chaque Program ID Core possède une forme texte et une forme typée :
```rust
let system_text: &str = ksp_core_lib::PRGID_SOLANA_SYSTEM;
let system_pubkey: ksp_core_lib::Pubkey =
ksp_core_lib::PRGIDPK_SOLANA_SYSTEM;
assert_eq!(
system_pubkey,
ksp_core_lib::Pubkey::from_str_const(system_text),
);
```
Les constantes `PRGID_*` sont utiles pour les wires, diagnostics sûrs ou comparaisons texte. Les constantes `PRGIDPK_*` sont préférées dès qu'un contrat manipule une adresse Solana typée.
## Déclarer une paire texte / `Pubkey`
`declare_program_id!` permet à une couche KSP propriétaire d'un Program ID de déclarer les deux représentations depuis une seule valeur Base58 :
```rust
ksp_core_lib::declare_program_id!(
PRGID_EXAMPLE,
PRGIDPK_EXAMPLE,
"11111111111111111111111111111111"
);
assert_eq!(PRGID_EXAMPLE, ksp_core_lib::PRGID_SOLANA_SYSTEM);
assert_eq!(PRGIDPK_EXAMPLE, ksp_core_lib::PRGIDPK_SOLANA_SYSTEM);
```
La macro ne signifie pas que toute nouvelle constante doit être ajoutée au registre Core. La couche propriétaire du domaine décide où vit le nouveau Program ID.
## Parcourir le registre canonique
`entries()` retourne le registre complet en ordre déterministe :
```rust
for entry in ksp_core_lib::entries() {
let code = entry.code();
let name = entry.name();
let text = entry.program_id();
let pubkey = entry.pubkey();
let domain = entry.domain();
let family = entry.family();
let protocol = entry.protocol();
let subfamily = entry.subfamily();
let program_version = entry.program_version();
let kind = entry.kind();
let _ = (
code,
name,
text,
pubkey,
domain,
family,
protocol,
subfamily,
program_version,
kind,
);
}
```
`native_program_ids()` fournit la vue des Program IDs Solana fondamentaux actuellement enregistrés :
```rust
let count = ksp_core_lib::native_program_ids().count();
assert_eq!(count, 18);
```
## Rechercher une entrée
Recherche par représentation Base58 :
```rust
let entry = ksp_core_lib::find_program_id(
ksp_core_lib::PRGID_SOLANA_SYSTEM,
);
let entry = match entry {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
assert_eq!(entry.code(), "solana.system");
```
Recherche par `Pubkey` :
```rust
let entry = ksp_core_lib::find_program_pubkey(
&ksp_core_lib::PRGIDPK_SOLANA_VOTE,
);
assert!(entry.is_some());
```
Une recherche inconnue retourne `None`; le registre n'invente pas une entrée générique.
## Filtrer par axe simple
Les helpers spécialisés conviennent aux filtres simples :
```rust
let loaders = ksp_core_lib::program_ids_by_family("loader");
for loader in loaders {
assert_eq!(loader.family(), "loader");
}
```
Les vues disponibles peuvent être consommées directement :
```rust
let solana_entries = ksp_core_lib::program_ids_by_domain("solana").count();
let loaders = ksp_core_lib::program_ids_by_family("loader").count();
let solana_protocol = ksp_core_lib::program_ids_by_protocol("solana").count();
assert_eq!(solana_entries, 18);
assert_eq!(loaders, 5);
assert_eq!(solana_protocol, 18);
```
## Combiner plusieurs axes
`ProgramIdFilter` compose les contraintes sans allocation de collection intermédiaire :
```rust
let filter = ksp_core_lib::ProgramIdFilter::new()
.with_domain("solana")
.with_family("loader")
.with_protocol("solana")
.with_subfamily("bpf")
.with_program_version("v2")
.with_kind(ksp_core_lib::ProgramIdKind::Loader);
for entry in ksp_core_lib::program_ids(filter) {
assert_eq!(entry.domain(), "solana");
assert_eq!(entry.family(), "loader");
assert_eq!(entry.protocol(), "solana");
assert_eq!(entry.subfamily(), std::option::Option::Some("bpf"));
assert_eq!(entry.program_version(), std::option::Option::Some("v2"));
assert_eq!(entry.kind(), ksp_core_lib::ProgramIdKind::Loader);
}
```
Un `ProgramIdFilter::new()` vide correspond à toutes les entrées du registre.
## Choisir entre texte, `Pubkey` et descriptor
Utiliser :
```text
PRGID_* / &str pour une représentation Base58 canonique
PRGIDPK_* / Pubkey pour un contrat Solana typé
ProgramIdEntry lorsqu'il faut aussi la taxonomie KSP
```
Ne pas reparcourir les constantes ou reconstruire une taxonomie parallèle dans une crate consommatrice lorsque le registre Core fournit déjà l'information requise.