v0.2.14-pre.003
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
<!-- file: crates/ksp-program-api/README.md -->
|
||||
<!-- version: 1 -->
|
||||
<!-- version: 2 -->
|
||||
|
||||
# ksp-program-api
|
||||
|
||||
`ksp-program-api` est la façade publique ouverte du domaine Program KSP. Elle est destinée aux implémentations officielles futures comme aux crates Program externes et ne possède pas les implémentations concrètes.
|
||||
|
||||
La tranche initiale `0.2.14-pre.002` matérialise uniquement le scaffold et les types déjà possédés par les couches fondatrices. Aucun trait decoder n'est encore publié.
|
||||
À partir de `0.2.14-pre.003`, la foundation expose les types Core/Interface retenus ainsi que le vocabulaire minimal de reconnaissance et d'outcome de décodage. Le trait decoder reste réservé à la tranche suivante.
|
||||
|
||||
## Ownership
|
||||
|
||||
@@ -35,11 +35,43 @@ ProgramAccountMeta
|
||||
ProgramInstruction
|
||||
```
|
||||
|
||||
`ksp-program-api` les réexporte depuis son crate-root pour offrir une façade de consommation stable sans dupliquer leurs types ni transférer leur ownership.
|
||||
Program API possède désormais :
|
||||
|
||||
## Surface de scaffold
|
||||
```text
|
||||
ProgramInstructionRecognition
|
||||
ProgramInstructionDecodeOutcome<Decoded>
|
||||
```
|
||||
|
||||
La façade `pre.002` expose exactement :
|
||||
`ksp-program-api` réexporte l'ensemble depuis son crate-root pour offrir une façade de consommation stable sans dupliquer les types Core/Interface ni transférer leur ownership.
|
||||
|
||||
## Recognition
|
||||
|
||||
`ProgramInstructionRecognition` est `#[non_exhaustive]` et possède trois états :
|
||||
|
||||
```text
|
||||
NoMatch l'implémentation ne revendique pas l'instruction
|
||||
ProgramMatch le Program ou la famille correspond, sans reconnaissance exacte
|
||||
ExactMatch l'implémentation affirme un match instruction-local exact
|
||||
```
|
||||
|
||||
Cette reconnaissance ne contient aucun score, priorité, proof, confidence, discriminator textuel ou inventaire central de Programs.
|
||||
|
||||
## Decode outcome
|
||||
|
||||
`ProgramInstructionDecodeOutcome<Decoded>` est également `#[non_exhaustive]` :
|
||||
|
||||
```text
|
||||
Decoded(Decoded) valeur typée possédée par l'implémentation
|
||||
Unsupported instruction connue mais non supportée par cette capability
|
||||
```
|
||||
|
||||
Un futur `decode(...)` utilisera le `Result` Core : une erreur de validation/décodage restera donc `Err`, sans variante parallèle `Failed`.
|
||||
|
||||
Le `Debug` de l'outcome n'impose pas `Decoded: Debug` et n'affiche jamais la valeur `Decoded`. Il produit uniquement le nom sûr de l'état (`Decoded` ou `Unsupported`).
|
||||
|
||||
## Surface actuelle
|
||||
|
||||
La façade `pre.003` expose :
|
||||
|
||||
```text
|
||||
Error
|
||||
@@ -49,16 +81,16 @@ Result
|
||||
Pubkey
|
||||
ProgramAccountMeta
|
||||
ProgramInstruction
|
||||
ProgramInstructionRecognition
|
||||
ProgramInstructionDecodeOutcome<Decoded>
|
||||
```
|
||||
|
||||
Aucun module interne n'est public.
|
||||
|
||||
Les contrats suivants restent réservés aux tranches suivantes :
|
||||
Le contrat suivant reste réservé à `pre.004` :
|
||||
|
||||
```text
|
||||
ProgramInstructionRecognition pre.003
|
||||
ProgramInstructionDecodeOutcome<T> pre.003
|
||||
ProgramInstructionDecoder pre.004
|
||||
ProgramInstructionDecoder
|
||||
```
|
||||
|
||||
## Frontières
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<!-- file: crates/ksp-program-api/USAGE.md -->
|
||||
<!-- version: 1 -->
|
||||
<!-- version: 2 -->
|
||||
|
||||
# Usage de ksp-program-api
|
||||
|
||||
Cette page décrit le scaffold public disponible à partir de `0.2.14-pre.002`. Utiliser uniquement les exports du crate-root ; aucun module interne ne fait partie du contrat consommable.
|
||||
Cette page décrit la surface publique disponible à partir de `0.2.14-pre.003`. Utiliser uniquement les exports du crate-root ; aucun module interne ne fait partie du contrat consommable.
|
||||
|
||||
## Construire un input Program avec la façade
|
||||
|
||||
@@ -23,9 +23,61 @@ assert!(instruction.is_ok());
|
||||
|
||||
`Pubkey`, `ProgramAccountMeta` et `ProgramInstruction` conservent leur ownership Core/Interface. Program API fournit seulement une façade cohérente aux futures implémentations de capability Program.
|
||||
|
||||
## Représenter une reconnaissance
|
||||
|
||||
```rust
|
||||
let recognition = ksp_program_api::ProgramInstructionRecognition::ProgramMatch;
|
||||
|
||||
match recognition {
|
||||
ksp_program_api::ProgramInstructionRecognition::NoMatch => {}
|
||||
ksp_program_api::ProgramInstructionRecognition::ProgramMatch => {}
|
||||
ksp_program_api::ProgramInstructionRecognition::ExactMatch => {}
|
||||
_ => {}
|
||||
}
|
||||
```
|
||||
|
||||
Le wildcard est volontaire : l'enum est `#[non_exhaustive]` afin de ne pas transformer la foundation en vocabulaire fermé pour toujours.
|
||||
|
||||
## Représenter un outcome de décodage
|
||||
|
||||
Le type décodé reste possédé par l'implémentation :
|
||||
|
||||
```rust
|
||||
struct ExternalDecodedInstruction {
|
||||
opcode: u8,
|
||||
}
|
||||
|
||||
let outcome = ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(
|
||||
ExternalDecodedInstruction { opcode: 7_u8 },
|
||||
);
|
||||
|
||||
match outcome {
|
||||
ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(value) => {
|
||||
assert_eq!(value.opcode, 7_u8);
|
||||
}
|
||||
ksp_program_api::ProgramInstructionDecodeOutcome::Unsupported => {}
|
||||
_ => {}
|
||||
}
|
||||
```
|
||||
|
||||
Aucun `Any`, JSON ou enum centrale n'est nécessaire pour transporter ce type.
|
||||
|
||||
## Debug sûr
|
||||
|
||||
`ProgramInstructionDecodeOutcome<Decoded>` possède un `Debug` volontairement opaque sur la valeur décodée :
|
||||
|
||||
```rust
|
||||
struct SecretDecoded;
|
||||
|
||||
let outcome = ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(SecretDecoded);
|
||||
assert_eq!(std::format!("{outcome:?}"), "Decoded");
|
||||
```
|
||||
|
||||
`SecretDecoded` n'a même pas besoin d'implémenter `Debug`. Cela empêche un diagnostic générique de rendre accidentellement un payload externe.
|
||||
|
||||
## Utiliser le contrat d'erreur commun
|
||||
|
||||
Les types d'erreur Core sont également disponibles depuis la façade :
|
||||
Les types d'erreur Core restent disponibles depuis la façade :
|
||||
|
||||
```rust
|
||||
fn forward_result(
|
||||
@@ -35,21 +87,20 @@ fn forward_result(
|
||||
}
|
||||
```
|
||||
|
||||
Aucun type d'erreur Program spécifique n'est nécessaire au scaffold.
|
||||
Le futur `decode(...)` utilisera ce `Result`. Une erreur sera donc `Err(...)`, tandis que `Unsupported` signifie une instruction connue volontairement non prise en charge.
|
||||
|
||||
## Ce que `pre.002` ne fournit pas
|
||||
## Ce que `pre.003` ne fournit pas
|
||||
|
||||
Il n'existe encore aucun :
|
||||
|
||||
```text
|
||||
recognize(...)
|
||||
decode(...)
|
||||
ProgramInstructionRecognition
|
||||
ProgramInstructionDecodeOutcome
|
||||
ProgramInstructionDecoder
|
||||
program_ids(...)
|
||||
recognize(...) sur un trait
|
||||
decode(...) sur un trait
|
||||
registry de decoders
|
||||
payload générique JSON/Any
|
||||
execution preparer
|
||||
```
|
||||
|
||||
Ces éléments ne doivent pas être simulés côté consumer. Les contrats de recognition/outcome puis le trait decoder seront introduits dans leurs tranches dédiées.
|
||||
Ces éléments ne doivent pas être simulés côté consumer. Le trait decoder et la preuve d'implémentation externe sont réservés à `pre.004`.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-program-api/src/lib.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -7,11 +7,17 @@
|
||||
|
||||
//! Open Program contracts shared by KSP and external Program implementations.
|
||||
//!
|
||||
//! This initial scaffold exposes only the Core and Interface types selected by
|
||||
//! the `0.2.14` API model. Decoder behavior, recognition, decode outcomes,
|
||||
//! registries, codecs, runtime logging and execution preparation are added only
|
||||
//! by later contracts when their ownership is justified.
|
||||
//! The foundation exposes Core/Interface types plus the minimal instruction
|
||||
//! recognition and decode-outcome vocabulary. Decoder behavior, registries,
|
||||
//! codecs, runtime logging and execution preparation are added only by later
|
||||
//! contracts when their ownership is justified.
|
||||
|
||||
mod program_instruction_decode;
|
||||
|
||||
/// Result of a successful Program instruction decode attempt.
|
||||
pub use self::program_instruction_decode::ProgramInstructionDecodeOutcome;
|
||||
/// Recognition strength reported by one Program instruction implementation.
|
||||
pub use self::program_instruction_decode::ProgramInstructionRecognition;
|
||||
/// Common KSP error type used by Program-facing contracts.
|
||||
pub use ksp_core_lib::Error;
|
||||
/// Stable structured code identifying a KSP error category and condition.
|
||||
|
||||
50
crates/ksp-program-api/src/program_instruction_decode.rs
Normal file
50
crates/ksp-program-api/src/program_instruction_decode.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
// file: crates/ksp-program-api/src/program_instruction_decode.rs
|
||||
// version: 1
|
||||
|
||||
/// Recognition strength reported by one Program instruction implementation.
|
||||
///
|
||||
/// Recognition is intentionally instruction-local. It does not encode registry
|
||||
/// priority, a persisted proof, a textual discriminator or a global Program
|
||||
/// kind. [`Self::ExactMatch`] is an assertion made by the implementation for
|
||||
/// the current instruction, while [`Self::ProgramMatch`] only establishes the
|
||||
/// Program-level match.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum ProgramInstructionRecognition {
|
||||
/// The implementation does not claim the instruction.
|
||||
NoMatch,
|
||||
/// The Program or Program family matches, but the instruction is not proven exact.
|
||||
ProgramMatch,
|
||||
/// The implementation claims an exact instruction-local match.
|
||||
ExactMatch,
|
||||
}
|
||||
|
||||
/// Result of a successful Program instruction decode attempt.
|
||||
///
|
||||
/// Decode failures are represented by the surrounding KSP [`crate::Result`],
|
||||
/// not by a parallel failure variant. `Unsupported` is reserved for a known
|
||||
/// Program instruction that the implementation deliberately does not decode.
|
||||
///
|
||||
/// The custom [`std::fmt::Debug`] implementation never formats the `Decoded`
|
||||
/// value, so external decoded payloads are not exposed accidentally through
|
||||
/// generic diagnostics.
|
||||
#[non_exhaustive]
|
||||
pub enum ProgramInstructionDecodeOutcome<Decoded> {
|
||||
/// The instruction was decoded into the implementation-owned output type.
|
||||
Decoded(Decoded),
|
||||
/// The instruction is known but unsupported by this decode capability.
|
||||
Unsupported,
|
||||
}
|
||||
|
||||
impl<Decoded> std::fmt::Debug for ProgramInstructionDecodeOutcome<Decoded> {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Decoded(_) => return formatter.write_str("Decoded"),
|
||||
Self::Unsupported => return formatter.write_str("Unsupported"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "../unit_tests/program_instruction_decode.rs"]
|
||||
mod tests;
|
||||
@@ -1,7 +1,7 @@
|
||||
// file: crates/ksp-program-api/tests/dependency_boundary.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Dependency and declarative-surface canaries for the Program API scaffold.
|
||||
//! Dependency and declarative-surface canaries for the Program API foundation.
|
||||
|
||||
#[test]
|
||||
fn pre_002_manifest_has_exact_core_and_interface_runtime_dependencies() {
|
||||
@@ -46,23 +46,27 @@ fn pre_002_manifest_has_exact_core_and_interface_runtime_dependencies() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pre_002_crate_root_is_facade_only_without_decoder_runtime_surface() {
|
||||
fn pre_003_crate_root_adds_only_recognition_and_decode_outcome() {
|
||||
let crate_root = include_str!("../src/lib.rs");
|
||||
for required in ["Error", "ErrorCode", "ErrorContext", "Pubkey", "Result", "ProgramAccountMeta", "ProgramInstruction"] {
|
||||
for required in [
|
||||
"ProgramInstructionDecodeOutcome",
|
||||
"ProgramInstructionRecognition",
|
||||
"Error",
|
||||
"ErrorCode",
|
||||
"ErrorContext",
|
||||
"Pubkey",
|
||||
"Result",
|
||||
"ProgramAccountMeta",
|
||||
"ProgramInstruction",
|
||||
] {
|
||||
assert!(crate_root.contains(required), "required Program API facade export missing: {required}");
|
||||
}
|
||||
for forbidden in [
|
||||
"pub mod ",
|
||||
"ProgramInstructionRecognition",
|
||||
"ProgramInstructionDecodeOutcome",
|
||||
"ProgramInstructionDecoder",
|
||||
"ProgramExecutionPreparer",
|
||||
"TRACING_TARGET",
|
||||
"ksp_logging_lib",
|
||||
"serde",
|
||||
"Any",
|
||||
] {
|
||||
assert!(!crate_root.contains(forbidden), "forbidden pre.002 Program API surface detected: {forbidden}");
|
||||
for forbidden in ["pub mod ", "ProgramInstructionDecoder", "ProgramExecutionPreparer", "TRACING_TARGET", "ksp_logging_lib", "serde", "Any"] {
|
||||
assert!(!crate_root.contains(forbidden), "forbidden pre.003 Program API surface detected: {forbidden}");
|
||||
}
|
||||
let outcome_source = include_str!("../src/program_instruction_decode.rs");
|
||||
for forbidden in ["Ignored", "Failed", "serde", "Any"] {
|
||||
assert!(!outcome_source.contains(forbidden), "forbidden recognition/outcome concept detected: {forbidden}");
|
||||
}
|
||||
assert!(!std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src/constants.rs").exists());
|
||||
return;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// file: crates/ksp-program-api/tests/public_api.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Integration canaries for the public `ksp-program-api` scaffold.
|
||||
//! Integration canaries for the public `ksp-program-api` foundation.
|
||||
|
||||
fn consume_result(value: ksp_program_api::Result<ksp_program_api::Pubkey>) -> ksp_program_api::Result<ksp_program_api::Pubkey> {
|
||||
return value;
|
||||
@@ -31,3 +31,19 @@ fn public_pre_002_scaffold_does_not_require_private_modules() {
|
||||
assert!(!source.contains("pub mod "));
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn public_pre_003_recognition_and_decode_outcome_are_available_from_crate_root() {
|
||||
let recognition = ksp_program_api::ProgramInstructionRecognition::ProgramMatch;
|
||||
assert_eq!(std::format!("{recognition:?}"), "ProgramMatch");
|
||||
let decoded = ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(31_u16);
|
||||
assert_eq!(std::format!("{decoded:?}"), "Decoded");
|
||||
let decoded_value = match decoded {
|
||||
ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(value) => value,
|
||||
_ => 0_u16,
|
||||
};
|
||||
assert_eq!(decoded_value, 31_u16);
|
||||
let unsupported = ksp_program_api::ProgramInstructionDecodeOutcome::<u16>::Unsupported;
|
||||
assert_eq!(std::format!("{unsupported:?}"), "Unsupported");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
// file: crates/ksp-program-api/unit_tests/program_instruction_decode.rs
|
||||
// version: 1
|
||||
|
||||
#[test]
|
||||
fn recognition_variants_are_distinct_and_payload_free() {
|
||||
assert_ne!(crate::ProgramInstructionRecognition::NoMatch, crate::ProgramInstructionRecognition::ProgramMatch);
|
||||
assert_ne!(crate::ProgramInstructionRecognition::ProgramMatch, crate::ProgramInstructionRecognition::ExactMatch);
|
||||
assert_eq!(std::format!("{:?}", crate::ProgramInstructionRecognition::NoMatch), "NoMatch");
|
||||
assert_eq!(std::format!("{:?}", crate::ProgramInstructionRecognition::ProgramMatch), "ProgramMatch");
|
||||
assert_eq!(std::format!("{:?}", crate::ProgramInstructionRecognition::ExactMatch), "ExactMatch");
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_outcome_preserves_decoded_value_and_unsupported_state() {
|
||||
let decoded = crate::ProgramInstructionDecodeOutcome::Decoded(17_u64);
|
||||
let decoded_value = match decoded {
|
||||
crate::ProgramInstructionDecodeOutcome::Decoded(value) => value,
|
||||
crate::ProgramInstructionDecodeOutcome::Unsupported => 0_u64,
|
||||
};
|
||||
assert_eq!(decoded_value, 17_u64);
|
||||
let unsupported = crate::ProgramInstructionDecodeOutcome::<u64>::Unsupported;
|
||||
assert!(matches!(unsupported, crate::ProgramInstructionDecodeOutcome::Unsupported));
|
||||
return;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn decode_outcome_debug_never_requires_or_renders_decoded_debug() {
|
||||
struct ExternalDecoded {
|
||||
secret_marker: u8,
|
||||
}
|
||||
let decoded = crate::ProgramInstructionDecodeOutcome::Decoded(ExternalDecoded { secret_marker: 0xA7_u8 });
|
||||
assert_eq!(std::format!("{decoded:?}"), "Decoded");
|
||||
let secret_marker = match decoded {
|
||||
crate::ProgramInstructionDecodeOutcome::Decoded(value) => value.secret_marker,
|
||||
crate::ProgramInstructionDecodeOutcome::Unsupported => 0_u8,
|
||||
};
|
||||
assert_eq!(secret_marker, 0xA7_u8);
|
||||
assert_eq!(std::format!("{:?}", crate::ProgramInstructionDecodeOutcome::<ExternalDecoded>::Unsupported), "Unsupported");
|
||||
return;
|
||||
}
|
||||
Reference in New Issue
Block a user