v0.2.14-pre.003

This commit is contained in:
2026-08-28 14:10:49 +02:00
parent c1f61a380f
commit 27d4cb1f36
11 changed files with 588 additions and 68 deletions

View File

@@ -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

View File

@@ -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`.

View File

@@ -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.

View 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;

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;
}