v0.2.14-pre.004
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
# file: Cargo.toml
|
# file: Cargo.toml
|
||||||
# version: 315
|
# version: 316
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
members = ["crates/ksp-app-config-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-interface-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-program-api", "crates/ksp-wallet-lib"]
|
members = ["crates/ksp-app-config-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-interface-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-program-api", "crates/ksp-wallet-lib"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.2.14-pre.3"
|
version = "0.2.14-pre.4"
|
||||||
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,11 +1,11 @@
|
|||||||
<!-- file: crates/ksp-program-api/README.md -->
|
<!-- file: crates/ksp-program-api/README.md -->
|
||||||
<!-- version: 2 -->
|
<!-- version: 3 -->
|
||||||
|
|
||||||
# ksp-program-api
|
# 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.
|
`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.
|
||||||
|
|
||||||
À 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.
|
À partir de `0.2.14-pre.004`, la foundation expose les types Core/Interface retenus, le vocabulaire minimal de reconnaissance/décodage et le trait instruction-only `ProgramInstructionDecoder`.
|
||||||
|
|
||||||
## Ownership
|
## Ownership
|
||||||
|
|
||||||
@@ -35,18 +35,19 @@ ProgramAccountMeta
|
|||||||
ProgramInstruction
|
ProgramInstruction
|
||||||
```
|
```
|
||||||
|
|
||||||
Program API possède désormais :
|
Program API possède :
|
||||||
|
|
||||||
```text
|
```text
|
||||||
ProgramInstructionRecognition
|
ProgramInstructionRecognition
|
||||||
ProgramInstructionDecodeOutcome<Decoded>
|
ProgramInstructionDecodeOutcome<Decoded>
|
||||||
|
ProgramInstructionDecoder
|
||||||
```
|
```
|
||||||
|
|
||||||
`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.
|
`ksp-program-api` réexporte l'ensemble depuis son crate-root. Aucun module interne n'est public.
|
||||||
|
|
||||||
## Recognition
|
## Recognition
|
||||||
|
|
||||||
`ProgramInstructionRecognition` est `#[non_exhaustive]` et possède trois états :
|
`ProgramInstructionRecognition` est `#[non_exhaustive]` :
|
||||||
|
|
||||||
```text
|
```text
|
||||||
NoMatch l'implémentation ne revendique pas l'instruction
|
NoMatch l'implémentation ne revendique pas l'instruction
|
||||||
@@ -58,20 +59,35 @@ Cette reconnaissance ne contient aucun score, priorité, proof, confidence, disc
|
|||||||
|
|
||||||
## Decode outcome
|
## Decode outcome
|
||||||
|
|
||||||
`ProgramInstructionDecodeOutcome<Decoded>` est également `#[non_exhaustive]` :
|
`ProgramInstructionDecodeOutcome<Decoded>` est `#[non_exhaustive]` :
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Decoded(Decoded) valeur typée possédée par l'implémentation
|
Decoded(Decoded) valeur typée possédée par l'implémentation
|
||||||
Unsupported instruction connue mais non supportée par cette capability
|
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`.
|
Les échecs réels passent par le `Result` Core. Il n'existe aucune 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`).
|
Le `Debug` de l'outcome n'impose pas `Decoded: Debug` et n'affiche jamais la valeur `Decoded`.
|
||||||
|
|
||||||
|
## Decoder instruction-only
|
||||||
|
|
||||||
|
`ProgramInstructionDecoder` est un trait ouvert `Send + Sync` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
type Decoded
|
||||||
|
program_ids(&self) -> &[Pubkey]
|
||||||
|
recognize(&self, &ProgramInstruction) -> ProgramInstructionRecognition
|
||||||
|
decode(&self, &ProgramInstruction) -> Result<ProgramInstructionDecodeOutcome<Self::Decoded>>
|
||||||
|
```
|
||||||
|
|
||||||
|
Le type `Decoded` est possédé par l'implémentation. Un decoder externe peut utiliser un `Pubkey` absent du registry Core : aucun enum central, `Any`, JSON ou descriptor global n'est requis pour déclarer un Program.
|
||||||
|
|
||||||
|
Le trait ne fournit aucun default method et ne promet pas de registry dyn hétérogène. `program_ids` et `recognize` servent à la sélection explicite ; `decode` traite une instruction déjà sélectionnée pour le decoder.
|
||||||
|
|
||||||
## Surface actuelle
|
## Surface actuelle
|
||||||
|
|
||||||
La façade `pre.003` expose :
|
La façade `pre.004` expose :
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Error
|
Error
|
||||||
@@ -83,13 +99,6 @@ ProgramAccountMeta
|
|||||||
ProgramInstruction
|
ProgramInstruction
|
||||||
ProgramInstructionRecognition
|
ProgramInstructionRecognition
|
||||||
ProgramInstructionDecodeOutcome<Decoded>
|
ProgramInstructionDecodeOutcome<Decoded>
|
||||||
```
|
|
||||||
|
|
||||||
Aucun module interne n'est public.
|
|
||||||
|
|
||||||
Le contrat suivant reste réservé à `pre.004` :
|
|
||||||
|
|
||||||
```text
|
|
||||||
ProgramInstructionDecoder
|
ProgramInstructionDecoder
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<!-- file: crates/ksp-program-api/USAGE.md -->
|
<!-- file: crates/ksp-program-api/USAGE.md -->
|
||||||
<!-- version: 2 -->
|
<!-- version: 3 -->
|
||||||
|
|
||||||
# Usage de ksp-program-api
|
# Usage de ksp-program-api
|
||||||
|
|
||||||
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.
|
Cette page décrit la surface publique disponible à partir de `0.2.14-pre.004`. Utiliser uniquement les exports du crate-root ; aucun module interne ne fait partie du contrat consommable.
|
||||||
|
|
||||||
## Construire un input Program avec la façade
|
## Construire un input Program avec la façade
|
||||||
|
|
||||||
@@ -21,12 +21,70 @@ let instruction = ksp_program_api::ProgramInstruction::try_new(
|
|||||||
assert!(instruction.is_ok());
|
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.
|
`Pubkey`, `ProgramAccountMeta` et `ProgramInstruction` conservent leur ownership Core/Interface.
|
||||||
|
|
||||||
## Représenter une reconnaissance
|
## Implémenter un decoder externe
|
||||||
|
|
||||||
|
Le type décodé reste entièrement possédé par la crate d'implémentation :
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let recognition = ksp_program_api::ProgramInstructionRecognition::ProgramMatch;
|
struct ExternalDecodedInstruction {
|
||||||
|
opcode: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ExternalDecoder {
|
||||||
|
program_ids: [ksp_program_api::Pubkey; 1],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ksp_program_api::ProgramInstructionDecoder for ExternalDecoder {
|
||||||
|
type Decoded = ExternalDecodedInstruction;
|
||||||
|
|
||||||
|
fn program_ids(&self) -> &[ksp_program_api::Pubkey] {
|
||||||
|
return &self.program_ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recognize(
|
||||||
|
&self,
|
||||||
|
instruction: &ksp_program_api::ProgramInstruction,
|
||||||
|
) -> ksp_program_api::ProgramInstructionRecognition {
|
||||||
|
if instruction.program_id() != &self.program_ids[0] {
|
||||||
|
return ksp_program_api::ProgramInstructionRecognition::NoMatch;
|
||||||
|
}
|
||||||
|
return ksp_program_api::ProgramInstructionRecognition::ProgramMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode(
|
||||||
|
&self,
|
||||||
|
instruction: &ksp_program_api::ProgramInstruction,
|
||||||
|
) -> ksp_program_api::Result<ksp_program_api::ProgramInstructionDecodeOutcome<Self::Decoded>> {
|
||||||
|
let opcode = match instruction.data().first() {
|
||||||
|
std::option::Option::Some(value) => *value,
|
||||||
|
std::option::Option::None => {
|
||||||
|
return std::result::Result::Ok(
|
||||||
|
ksp_program_api::ProgramInstructionDecodeOutcome::Unsupported,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return std::result::Result::Ok(
|
||||||
|
ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(
|
||||||
|
ExternalDecodedInstruction { opcode },
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun `ksp-program-lib`, enum centrale, `Any`, JSON ou codec n'est nécessaire. Le Program ID peut être un `Pubkey` opaque non enregistré par Core.
|
||||||
|
|
||||||
|
## Sélection explicite
|
||||||
|
|
||||||
|
La sélection reste distincte du décodage :
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let recognition = ksp_program_api::ProgramInstructionDecoder::recognize(
|
||||||
|
&decoder,
|
||||||
|
&instruction,
|
||||||
|
);
|
||||||
|
|
||||||
match recognition {
|
match recognition {
|
||||||
ksp_program_api::ProgramInstructionRecognition::NoMatch => {}
|
ksp_program_api::ProgramInstructionRecognition::NoMatch => {}
|
||||||
@@ -36,35 +94,30 @@ match recognition {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Le wildcard est volontaire : l'enum est `#[non_exhaustive]` afin de ne pas transformer la foundation en vocabulaire fermé pour toujours.
|
L'enum est `#[non_exhaustive]`. `decode` n'est pas un substitut à `recognize` : il traite une instruction déjà sélectionnée pour le decoder.
|
||||||
|
|
||||||
## Représenter un outcome de décodage
|
## Outcome et erreur
|
||||||
|
|
||||||
Le type décodé reste possédé par l'implémentation :
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
struct ExternalDecodedInstruction {
|
let outcome = ksp_program_api::ProgramInstructionDecoder::decode(
|
||||||
opcode: u8,
|
&decoder,
|
||||||
}
|
&instruction,
|
||||||
|
)?;
|
||||||
let outcome = ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(
|
|
||||||
ExternalDecodedInstruction { opcode: 7_u8 },
|
|
||||||
);
|
|
||||||
|
|
||||||
match outcome {
|
match outcome {
|
||||||
ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(value) => {
|
ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(value) => {
|
||||||
assert_eq!(value.opcode, 7_u8);
|
let _opcode = value.opcode;
|
||||||
}
|
}
|
||||||
ksp_program_api::ProgramInstructionDecodeOutcome::Unsupported => {}
|
ksp_program_api::ProgramInstructionDecodeOutcome::Unsupported => {}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Aucun `Any`, JSON ou enum centrale n'est nécessaire pour transporter ce type.
|
Une erreur réelle est un `Err(ksp_program_api::Error)`. `Unsupported` n'est pas une deuxième forme d'erreur : il indique qu'une instruction reconnue n'est volontairement pas décodée par cette capability.
|
||||||
|
|
||||||
## Debug sûr
|
## Debug sûr
|
||||||
|
|
||||||
`ProgramInstructionDecodeOutcome<Decoded>` possède un `Debug` volontairement opaque sur la valeur décodée :
|
`ProgramInstructionDecodeOutcome<Decoded>` possède un `Debug` volontairement opaque :
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
struct SecretDecoded;
|
struct SecretDecoded;
|
||||||
@@ -73,34 +126,22 @@ let outcome = ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(SecretDe
|
|||||||
assert_eq!(std::format!("{outcome:?}"), "Decoded");
|
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.
|
`SecretDecoded` n'a pas besoin d'implémenter `Debug` et sa valeur n'est jamais rendue par l'outcome.
|
||||||
|
|
||||||
## Utiliser le contrat d'erreur commun
|
## Hors surface `pre.004`
|
||||||
|
|
||||||
Les types d'erreur Core restent disponibles depuis la façade :
|
Il n'existe toujours aucun :
|
||||||
|
|
||||||
```rust
|
|
||||||
fn forward_result(
|
|
||||||
value: ksp_program_api::Result<ksp_program_api::ProgramInstruction>,
|
|
||||||
) -> ksp_program_api::Result<ksp_program_api::ProgramInstruction> {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
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.003` ne fournit pas
|
|
||||||
|
|
||||||
Il n'existe encore aucun :
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
ProgramInstructionDecoder
|
|
||||||
program_ids(...)
|
|
||||||
recognize(...) sur un trait
|
|
||||||
decode(...) sur un trait
|
|
||||||
registry de decoders
|
registry de decoders
|
||||||
payload générique JSON/Any
|
composition dyn hétérogène
|
||||||
execution preparer
|
identity/version/coverage descriptor
|
||||||
|
payload canonique D3
|
||||||
|
ProgramAccountDecoder / Event / ReturnData
|
||||||
|
ProgramExecutionPreparer
|
||||||
|
execution policy
|
||||||
|
serde / JSON / codec
|
||||||
|
logging / runtime réseau
|
||||||
```
|
```
|
||||||
|
|
||||||
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`.
|
Ces surfaces ne doivent pas être simulées côté consumer. Elles attendent les vertical slices qui justifieront leurs contrats réels.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-program-api/src/lib.rs
|
// file: crates/ksp-program-api/src/lib.rs
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![deny(unreachable_pub)]
|
#![deny(unreachable_pub)]
|
||||||
@@ -8,16 +8,19 @@
|
|||||||
//! Open Program contracts shared by KSP and external Program implementations.
|
//! Open Program contracts shared by KSP and external Program implementations.
|
||||||
//!
|
//!
|
||||||
//! The foundation exposes Core/Interface types plus the minimal instruction
|
//! The foundation exposes Core/Interface types plus the minimal instruction
|
||||||
//! recognition and decode-outcome vocabulary. Decoder behavior, registries,
|
//! recognition/decode vocabulary and the instruction decoder trait. Registries,
|
||||||
//! codecs, runtime logging and execution preparation are added only by later
|
//! codecs, runtime logging and execution preparation remain outside this
|
||||||
//! contracts when their ownership is justified.
|
//! foundation until their ownership is justified.
|
||||||
|
|
||||||
mod program_instruction_decode;
|
mod program_instruction_decode;
|
||||||
|
mod program_instruction_decoder;
|
||||||
|
|
||||||
/// Result of a successful Program instruction decode attempt.
|
/// Result of a successful Program instruction decode attempt.
|
||||||
pub use self::program_instruction_decode::ProgramInstructionDecodeOutcome;
|
pub use self::program_instruction_decode::ProgramInstructionDecodeOutcome;
|
||||||
/// Recognition strength reported by one Program instruction implementation.
|
/// Recognition strength reported by one Program instruction implementation.
|
||||||
pub use self::program_instruction_decode::ProgramInstructionRecognition;
|
pub use self::program_instruction_decode::ProgramInstructionRecognition;
|
||||||
|
/// Open contract implemented by one Program instruction decoder.
|
||||||
|
pub use self::program_instruction_decoder::ProgramInstructionDecoder;
|
||||||
/// Common KSP error type used by Program-facing contracts.
|
/// Common KSP error type used by Program-facing contracts.
|
||||||
pub use ksp_core_lib::Error;
|
pub use ksp_core_lib::Error;
|
||||||
/// Stable structured code identifying a KSP error category and condition.
|
/// Stable structured code identifying a KSP error category and condition.
|
||||||
|
|||||||
28
crates/ksp-program-api/src/program_instruction_decoder.rs
Normal file
28
crates/ksp-program-api/src/program_instruction_decoder.rs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
// file: crates/ksp-program-api/src/program_instruction_decoder.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
/// Open contract implemented by one Program instruction decoder.
|
||||||
|
///
|
||||||
|
/// The decoder owns its concrete [`Self::Decoded`] type. No central Program
|
||||||
|
/// enum, erased `Any` payload or serialization contract is required. Program
|
||||||
|
/// identifiers remain opaque [`crate::Pubkey`] values and do not need to be
|
||||||
|
/// registered by Core.
|
||||||
|
///
|
||||||
|
/// Candidate selection is explicit: callers use [`Self::program_ids`] and
|
||||||
|
/// [`Self::recognize`] before invoking [`Self::decode`]. `decode` therefore
|
||||||
|
/// reports only a successful typed value, an intentional unsupported state, or
|
||||||
|
/// a KSP [`crate::Result`] error. The trait defines no default methods and does
|
||||||
|
/// not promise heterogeneous runtime object composition.
|
||||||
|
pub trait ProgramInstructionDecoder: Send + Sync {
|
||||||
|
/// Concrete decoded instruction type owned by the implementation.
|
||||||
|
type Decoded;
|
||||||
|
|
||||||
|
/// Returns the opaque Program identifiers claimed by this decoder.
|
||||||
|
fn program_ids(&self) -> &[crate::Pubkey];
|
||||||
|
|
||||||
|
/// Reports how strongly this decoder recognizes one bounded instruction.
|
||||||
|
fn recognize(&self, instruction: &crate::ProgramInstruction) -> crate::ProgramInstructionRecognition;
|
||||||
|
|
||||||
|
/// Decodes one instruction already selected for this decoder.
|
||||||
|
fn decode(&self, instruction: &crate::ProgramInstruction) -> crate::Result<crate::ProgramInstructionDecodeOutcome<Self::Decoded>>;
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-program-api/tests/dependency_boundary.rs
|
// file: crates/ksp-program-api/tests/dependency_boundary.rs
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
//! Dependency and declarative-surface canaries for the Program API foundation.
|
//! Dependency and declarative-surface canaries for the Program API foundation.
|
||||||
|
|
||||||
@@ -46,10 +46,11 @@ fn pre_002_manifest_has_exact_core_and_interface_runtime_dependencies() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pre_003_crate_root_adds_only_recognition_and_decode_outcome() {
|
fn pre_004_crate_root_adds_decoder_without_runtime_surface() {
|
||||||
let crate_root = include_str!("../src/lib.rs");
|
let crate_root = include_str!("../src/lib.rs");
|
||||||
for required in [
|
for required in [
|
||||||
"ProgramInstructionDecodeOutcome",
|
"ProgramInstructionDecodeOutcome",
|
||||||
|
"ProgramInstructionDecoder",
|
||||||
"ProgramInstructionRecognition",
|
"ProgramInstructionRecognition",
|
||||||
"Error",
|
"Error",
|
||||||
"ErrorCode",
|
"ErrorCode",
|
||||||
@@ -61,9 +62,18 @@ fn pre_003_crate_root_adds_only_recognition_and_decode_outcome() {
|
|||||||
] {
|
] {
|
||||||
assert!(crate_root.contains(required), "required Program API facade export missing: {required}");
|
assert!(crate_root.contains(required), "required Program API facade export missing: {required}");
|
||||||
}
|
}
|
||||||
for forbidden in ["pub mod ", "ProgramInstructionDecoder", "ProgramExecutionPreparer", "TRACING_TARGET", "ksp_logging_lib", "serde", "Any"] {
|
for forbidden in ["pub mod ", "ProgramExecutionPreparer", "TRACING_TARGET", "ksp_logging_lib", "serde", "Any"] {
|
||||||
assert!(!crate_root.contains(forbidden), "forbidden pre.003 Program API surface detected: {forbidden}");
|
assert!(!crate_root.contains(forbidden), "forbidden pre.004 Program API surface detected: {forbidden}");
|
||||||
}
|
}
|
||||||
|
let decoder_source = include_str!("../src/program_instruction_decoder.rs");
|
||||||
|
assert!(decoder_source.contains("pub trait ProgramInstructionDecoder: Send + Sync"));
|
||||||
|
assert!(decoder_source.contains("type Decoded;"));
|
||||||
|
assert!(decoder_source.contains("fn program_ids(&self) -> &[crate::Pubkey];"));
|
||||||
|
assert!(decoder_source.contains("fn recognize(&self, instruction: &crate::ProgramInstruction)"));
|
||||||
|
assert!(decoder_source.contains("crate::Result<crate::ProgramInstructionDecodeOutcome<Self::Decoded>>"));
|
||||||
|
assert!(!decoder_source.contains("ProgramExecutionPreparer"));
|
||||||
|
assert!(!decoder_source.contains("serde"));
|
||||||
|
assert!(!decoder_source.contains("std::any::Any"));
|
||||||
let outcome_source = include_str!("../src/program_instruction_decode.rs");
|
let outcome_source = include_str!("../src/program_instruction_decode.rs");
|
||||||
for forbidden in ["Ignored", "Failed", "serde", "Any"] {
|
for forbidden in ["Ignored", "Failed", "serde", "Any"] {
|
||||||
assert!(!outcome_source.contains(forbidden), "forbidden recognition/outcome concept detected: {forbidden}");
|
assert!(!outcome_source.contains(forbidden), "forbidden recognition/outcome concept detected: {forbidden}");
|
||||||
|
|||||||
115
crates/ksp-program-api/tests/external_implementation.rs
Normal file
115
crates/ksp-program-api/tests/external_implementation.rs
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
// file: crates/ksp-program-api/tests/external_implementation.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
//! Downstream-style implementation canary for the open Program decoder contract.
|
||||||
|
|
||||||
|
const EXTERNAL_PROGRAM_ID_BYTES: [u8; 32] = [0xE7_u8; 32];
|
||||||
|
const FOREIGN_PROGRAM_ID_BYTES: [u8; 32] = [0xE8_u8; 32];
|
||||||
|
const SUPPORTED_OPCODE: u8 = 0x2A_u8;
|
||||||
|
|
||||||
|
struct ExternalDecodedInstruction {
|
||||||
|
opcode: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ExternalProgramDecoder {
|
||||||
|
program_ids: [ksp_program_api::Pubkey; 1],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExternalProgramDecoder {
|
||||||
|
fn new() -> Self {
|
||||||
|
return Self { program_ids: [ksp_program_api::Pubkey::new_from_array(EXTERNAL_PROGRAM_ID_BYTES)] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ksp_program_api::ProgramInstructionDecoder for ExternalProgramDecoder {
|
||||||
|
type Decoded = ExternalDecodedInstruction;
|
||||||
|
|
||||||
|
fn program_ids(&self) -> &[ksp_program_api::Pubkey] {
|
||||||
|
return &self.program_ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recognize(&self, instruction: &ksp_program_api::ProgramInstruction) -> ksp_program_api::ProgramInstructionRecognition {
|
||||||
|
if instruction.program_id() != &self.program_ids[0] {
|
||||||
|
return ksp_program_api::ProgramInstructionRecognition::NoMatch;
|
||||||
|
}
|
||||||
|
if instruction.data().first() == std::option::Option::Some(&SUPPORTED_OPCODE) {
|
||||||
|
return ksp_program_api::ProgramInstructionRecognition::ExactMatch;
|
||||||
|
}
|
||||||
|
return ksp_program_api::ProgramInstructionRecognition::ProgramMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode(
|
||||||
|
&self,
|
||||||
|
instruction: &ksp_program_api::ProgramInstruction,
|
||||||
|
) -> ksp_program_api::Result<ksp_program_api::ProgramInstructionDecodeOutcome<Self::Decoded>> {
|
||||||
|
if instruction.program_id() != &self.program_ids[0] {
|
||||||
|
return std::result::Result::Ok(ksp_program_api::ProgramInstructionDecodeOutcome::Unsupported);
|
||||||
|
}
|
||||||
|
let opcode = match instruction.data().first() {
|
||||||
|
std::option::Option::Some(value) if *value == SUPPORTED_OPCODE => *value,
|
||||||
|
_ => return std::result::Result::Ok(ksp_program_api::ProgramInstructionDecodeOutcome::Unsupported),
|
||||||
|
};
|
||||||
|
return std::result::Result::Ok(ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(ExternalDecodedInstruction { opcode }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_send_sync<T: Send + Sync>(_value: &T) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_004_external_decoder_uses_unregistered_pubkey_and_implementation_owned_output() {
|
||||||
|
let decoder = ExternalProgramDecoder::new();
|
||||||
|
assert_send_sync(&decoder);
|
||||||
|
let external_program_id = ksp_program_api::Pubkey::new_from_array(EXTERNAL_PROGRAM_ID_BYTES);
|
||||||
|
assert!(ksp_core_lib::find_program_pubkey(&external_program_id).is_none());
|
||||||
|
assert_eq!(ksp_program_api::ProgramInstructionDecoder::program_ids(&decoder), &[external_program_id]);
|
||||||
|
let exact = ksp_program_api::ProgramInstruction::try_new(external_program_id, std::vec![], std::vec![SUPPORTED_OPCODE]);
|
||||||
|
assert!(exact.is_ok());
|
||||||
|
let exact = match exact {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(ksp_program_api::ProgramInstructionDecoder::recognize(&decoder, &exact), ksp_program_api::ProgramInstructionRecognition::ExactMatch);
|
||||||
|
let outcome = ksp_program_api::ProgramInstructionDecoder::decode(&decoder, &exact);
|
||||||
|
assert!(outcome.is_ok());
|
||||||
|
let outcome = match outcome {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
let decoded = match outcome {
|
||||||
|
ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(value) => value,
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
assert_eq!(decoded.opcode, SUPPORTED_OPCODE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_004_external_decoder_distinguishes_program_match_unsupported_and_no_match() {
|
||||||
|
let decoder = ExternalProgramDecoder::new();
|
||||||
|
let external_program_id = ksp_program_api::Pubkey::new_from_array(EXTERNAL_PROGRAM_ID_BYTES);
|
||||||
|
let program_only = ksp_program_api::ProgramInstruction::try_new(external_program_id, std::vec![], std::vec![0x11_u8]);
|
||||||
|
assert!(program_only.is_ok());
|
||||||
|
let program_only = match program_only {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(ksp_program_api::ProgramInstructionDecoder::recognize(&decoder, &program_only), ksp_program_api::ProgramInstructionRecognition::ProgramMatch);
|
||||||
|
let unsupported = ksp_program_api::ProgramInstructionDecoder::decode(&decoder, &program_only);
|
||||||
|
assert!(unsupported.is_ok());
|
||||||
|
let unsupported = match unsupported {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert!(matches!(unsupported, ksp_program_api::ProgramInstructionDecodeOutcome::Unsupported));
|
||||||
|
let foreign_program_id = ksp_program_api::Pubkey::new_from_array(FOREIGN_PROGRAM_ID_BYTES);
|
||||||
|
let foreign = ksp_program_api::ProgramInstruction::try_new(foreign_program_id, std::vec![], std::vec![SUPPORTED_OPCODE]);
|
||||||
|
assert!(foreign.is_ok());
|
||||||
|
let foreign = match foreign {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(ksp_program_api::ProgramInstructionDecoder::recognize(&decoder, &foreign), ksp_program_api::ProgramInstructionRecognition::NoMatch);
|
||||||
|
return;
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-program-api/tests/public_api.rs
|
// file: crates/ksp-program-api/tests/public_api.rs
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
//! Integration canaries for the public `ksp-program-api` foundation.
|
//! Integration canaries for the public `ksp-program-api` foundation.
|
||||||
|
|
||||||
@@ -47,3 +47,31 @@ fn public_pre_003_recognition_and_decode_outcome_are_available_from_crate_root()
|
|||||||
assert_eq!(std::format!("{unsupported:?}"), "Unsupported");
|
assert_eq!(std::format!("{unsupported:?}"), "Unsupported");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn public_pre_004_instruction_decoder_trait_is_available_from_crate_root() {
|
||||||
|
let decoder = NeverInstantiatedDecoder;
|
||||||
|
assert!(ksp_program_api::ProgramInstructionDecoder::program_ids(&decoder).is_empty());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NeverInstantiatedDecoder;
|
||||||
|
|
||||||
|
impl ksp_program_api::ProgramInstructionDecoder for NeverInstantiatedDecoder {
|
||||||
|
type Decoded = u8;
|
||||||
|
|
||||||
|
fn program_ids(&self) -> &[ksp_program_api::Pubkey] {
|
||||||
|
return &[];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recognize(&self, _instruction: &ksp_program_api::ProgramInstruction) -> ksp_program_api::ProgramInstructionRecognition {
|
||||||
|
return ksp_program_api::ProgramInstructionRecognition::NoMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode(
|
||||||
|
&self,
|
||||||
|
_instruction: &ksp_program_api::ProgramInstruction,
|
||||||
|
) -> ksp_program_api::Result<ksp_program_api::ProgramInstructionDecodeOutcome<Self::Decoded>> {
|
||||||
|
return std::result::Result::Ok(ksp_program_api::ProgramInstructionDecodeOutcome::Unsupported);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
264
deltas/0.2.14/pre.004.md
Normal file
264
deltas/0.2.14/pre.004.md
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
<!-- file: deltas/0.2.14/pre.004.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.2.14-pre.004` — decoder instruction-only + implémentation externe
|
||||||
|
|
||||||
|
## 1. Base requise
|
||||||
|
|
||||||
|
Cette tranche s'applique exclusivement sur :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0.2.13
|
||||||
|
+ 0.2.14-pre.001
|
||||||
|
+ 0.2.14-pre.002
|
||||||
|
+ 0.2.14-pre.003
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur fourni pour `pre.003` est intégralement vert :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all PASS
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py PASS
|
||||||
|
python3 scripts/audit_markdown_tables.py ... PASS — 171 tables / 120 fichiers
|
||||||
|
cargo check --workspace PASS
|
||||||
|
cargo clippy --workspace --all-targets PASS
|
||||||
|
cargo test -p ksp-program-api PASS — 8 tests Rust
|
||||||
|
cargo test --workspace PASS
|
||||||
|
cargo tree -p ksp-program-api --edges normal PASS — Core + Interface uniquement
|
||||||
|
cargo tree --duplicates exécuté
|
||||||
|
```
|
||||||
|
|
||||||
|
La version workspace passe de :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.14-pre.3
|
||||||
|
```
|
||||||
|
|
||||||
|
à :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.14-pre.4
|
||||||
|
```
|
||||||
|
|
||||||
|
Commit attendu :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0.2.14-pre.004
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Objectif
|
||||||
|
|
||||||
|
Matérialiser le contrat instruction-only ouvert retenu par `pre.001` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ProgramInstructionDecoder: Send + Sync
|
||||||
|
associated type Decoded
|
||||||
|
program_ids(&self) -> &[Pubkey]
|
||||||
|
recognize(&self, &ProgramInstruction) -> ProgramInstructionRecognition
|
||||||
|
decode(&self, &ProgramInstruction) -> Result<ProgramInstructionDecodeOutcome<Self::Decoded>>
|
||||||
|
```
|
||||||
|
|
||||||
|
Puis prouver qu'une crate consommatrice séparée peut l'implémenter avec son propre type décodé et un Program `Pubkey` absent du registry Core.
|
||||||
|
|
||||||
|
## 3. Trait `ProgramInstructionDecoder`
|
||||||
|
|
||||||
|
Le trait est défini dans un module privé et réexporté depuis le crate-root.
|
||||||
|
|
||||||
|
Propriétés :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Send + Sync requis sur l'implémentation
|
||||||
|
Decoded associated type sans bound imposé
|
||||||
|
program_ids slice de Pubkey opaques
|
||||||
|
recognize sélection instruction-local explicite
|
||||||
|
decode Result Core + outcome générique
|
||||||
|
méthodes par défaut aucune
|
||||||
|
registry / object composition aucune promesse
|
||||||
|
```
|
||||||
|
|
||||||
|
`decode` est destiné à une instruction déjà sélectionnée via `program_ids` / `recognize`; il ne remplace pas le signal de reconnaissance.
|
||||||
|
|
||||||
|
## 4. Open-world Program IDs
|
||||||
|
|
||||||
|
Aucun `ProgramKind`, enum centrale ou validation contre le registry Core n'est introduit.
|
||||||
|
|
||||||
|
Le canari externe utilise :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Pubkey::new_from_array([0xE7; 32])
|
||||||
|
```
|
||||||
|
|
||||||
|
et vérifie explicitement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp_core_lib::find_program_pubkey(&external_program_id) == None
|
||||||
|
```
|
||||||
|
|
||||||
|
Cette consultation du registry est limitée au test négatif. `ksp-program-api` ne réexporte pas `find_program_pubkey` et le decoder externe n'a besoin que de la façade Program API pour son implémentation.
|
||||||
|
|
||||||
|
## 5. Associated output externe
|
||||||
|
|
||||||
|
Le test d'intégration définit hors du code de production :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ExternalDecodedInstruction
|
||||||
|
ExternalProgramDecoder
|
||||||
|
```
|
||||||
|
|
||||||
|
`ExternalProgramDecoder` implémente le trait et produit :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ExactMatch pour l'opcode supporté
|
||||||
|
ProgramMatch pour le Program connu avec opcode non supporté
|
||||||
|
NoMatch pour un autre Program
|
||||||
|
Decoded(...) pour l'opcode supporté
|
||||||
|
Unsupported pour le Program connu mais non supporté
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun `Any`, JSON, serde, codec ou enum centrale n'intervient dans le transport du type décodé.
|
||||||
|
|
||||||
|
## 6. Dependency firewall
|
||||||
|
|
||||||
|
Le manifest de `ksp-program-api` reste inchangé :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-core-lib
|
||||||
|
ksp-interface-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
Toujours absents :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-program-lib
|
||||||
|
ksp-logging-lib
|
||||||
|
Transport / Config / Wallet / Store / Materializer
|
||||||
|
serde / serde_json
|
||||||
|
borsh / bincode / wincode
|
||||||
|
solana-instruction
|
||||||
|
reqwest / tokio / tonic / tauri / tracing
|
||||||
|
```
|
||||||
|
|
||||||
|
## 7. Tests
|
||||||
|
|
||||||
|
### Public API
|
||||||
|
|
||||||
|
`tests/public_api.rs` prouve que le trait est implémentable depuis la façade crate-root sans module privé.
|
||||||
|
|
||||||
|
### External implementation
|
||||||
|
|
||||||
|
`tests/external_implementation.rs` est compilé par Cargo comme crate d'intégration séparée et vérifie :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Send + Sync de l'implémentation
|
||||||
|
associated output tiers
|
||||||
|
Program Pubkey non enregistré
|
||||||
|
program_ids
|
||||||
|
NoMatch / ProgramMatch / ExactMatch
|
||||||
|
Decoded / Unsupported
|
||||||
|
absence de ksp-program-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
### Boundary
|
||||||
|
|
||||||
|
`tests/dependency_boundary.rs` avance l'inventaire autorisé jusqu'au trait et maintient l'absence de preparer, logging, serde, `Any` et module public.
|
||||||
|
|
||||||
|
## 8. Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-program-api/src/program_instruction_decoder.rs
|
||||||
|
crates/ksp-program-api/tests/external_implementation.rs
|
||||||
|
deltas/0.2.14/pre.004.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-program-api/README.md
|
||||||
|
crates/ksp-program-api/USAGE.md
|
||||||
|
crates/ksp-program-api/src/lib.rs
|
||||||
|
crates/ksp-program-api/tests/dependency_boundary.rs
|
||||||
|
crates/ksp-program-api/tests/public_api.rs
|
||||||
|
docs/plans/021-V0_2_14_PROGRAM_API_PLAN.md
|
||||||
|
docs/validation/017-V0_2_14_PROGRAM_API.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 10. Fichiers volontairement inchangés
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-program-api/Cargo.toml
|
||||||
|
README.md
|
||||||
|
RULES.md
|
||||||
|
ROADMAP.md
|
||||||
|
CHANGELOG.md
|
||||||
|
docs/architecture/**
|
||||||
|
docs/rules/**
|
||||||
|
crates/ksp-core-lib/**
|
||||||
|
crates/ksp-interface-lib/**
|
||||||
|
crates/ksp-logging-lib/**
|
||||||
|
crates/ksp-*-transport-lib/**
|
||||||
|
crates/ksp-config-lib/**
|
||||||
|
crates/ksp-wallet-lib/**
|
||||||
|
crates/ksp-app-*/**
|
||||||
|
prompts/**
|
||||||
|
```
|
||||||
|
|
||||||
|
## 11. Scope négatif maintenu
|
||||||
|
|
||||||
|
Cette tranche n'introduit pas :
|
||||||
|
|
||||||
|
```text
|
||||||
|
runtime decoder registry
|
||||||
|
Vec<Box<dyn ProgramInstructionDecoder>>
|
||||||
|
object-safety promise
|
||||||
|
identity/version/coverage descriptor
|
||||||
|
priority/conflict policy
|
||||||
|
ProgramAccountDecoder / Event / ReturnData
|
||||||
|
payload canonique D3
|
||||||
|
serde / JSON / Any
|
||||||
|
proof/confidence contextuels
|
||||||
|
ProgramExecutionPreparer
|
||||||
|
ExecutionPolicy / Execution
|
||||||
|
```
|
||||||
|
|
||||||
|
## 12. Gate opérateur attendu
|
||||||
|
|
||||||
|
Après application :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo fmt --all
|
||||||
|
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.2.14
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-program-api
|
||||||
|
cargo test --workspace
|
||||||
|
cargo tree -p ksp-program-api --edges normal
|
||||||
|
cargo tree --duplicates
|
||||||
|
```
|
||||||
|
|
||||||
|
Le graphe normal doit rester :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-program-api
|
||||||
|
├── ksp-core-lib
|
||||||
|
└── ksp-interface-lib
|
||||||
|
└── ksp-core-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
Une correction découverte par ce gate reste un `0.2.14-pre.004-fix.NNN` et n'avance pas `pre.005`.
|
||||||
|
|
||||||
|
## 13. Suite
|
||||||
|
|
||||||
|
Après gate vert, `pre.005` doit rester une tranche de hardening/API completeness :
|
||||||
|
|
||||||
|
```text
|
||||||
|
bounds et sécurité Debug/error
|
||||||
|
inventaire exact des exports publics
|
||||||
|
absence de closed-world Program enum
|
||||||
|
absence de serde/codec/logging/runtime
|
||||||
|
scope négatif registry/preparer/payload D3
|
||||||
|
test de complétude release
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun nouveau contrat fonctionnel n'est prévu dans `pre.005`.
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/plans/021-V0_2_14_PROGRAM_API_PLAN.md -->
|
<!-- file: docs/plans/021-V0_2_14_PROGRAM_API_PLAN.md -->
|
||||||
<!-- version: 3 -->
|
<!-- version: 4 -->
|
||||||
|
|
||||||
# Plan `0.2.14` — Program API foundation
|
# Plan `0.2.14` — Program API foundation
|
||||||
|
|
||||||
@@ -549,13 +549,15 @@ Aucun trait decoder, recognition, outcome, registry, codec, runtime logging ou e
|
|||||||
|
|
||||||
### `pre.003` — Recognition + outcome minimal
|
### `pre.003` — Recognition + outcome minimal
|
||||||
|
|
||||||
**Statut : matérialisé ; gate opérateur à confirmer.**
|
**Statut : réalisé ; gate opérateur intégralement PASS.**
|
||||||
|
|
||||||
`ProgramInstructionRecognition` et `ProgramInstructionDecodeOutcome<Decoded>` sont ajoutés dans un module privé puis réexportés depuis le crate-root. Les deux enums sont `#[non_exhaustive]`. Le `Debug` de l'outcome est manuel, ne requiert pas `Decoded: Debug` et n'affiche jamais la valeur décodée. Aucun descriptor, registry ou trait decoder n'est avancé.
|
`ProgramInstructionRecognition` et `ProgramInstructionDecodeOutcome<Decoded>` sont ajoutés dans un module privé puis réexportés depuis le crate-root. Les deux enums sont `#[non_exhaustive]`. Le `Debug` de l'outcome est manuel, ne requiert pas `Decoded: Debug` et n'affiche jamais la valeur décodée. Aucun descriptor, registry ou trait decoder n'est avancé.
|
||||||
|
|
||||||
### `pre.004` — `ProgramInstructionDecoder` + external implementation
|
### `pre.004` — `ProgramInstructionDecoder` + external implementation
|
||||||
|
|
||||||
Introduire le trait `Send + Sync` avec associated output, `program_ids`, `recognize`, `decode`, puis prouver une implémentation depuis une crate externe avec Program Pubkey non enregistré.
|
**Statut : matérialisé ; gate opérateur à confirmer.**
|
||||||
|
|
||||||
|
Le trait `ProgramInstructionDecoder: Send + Sync` expose l'associated type `Decoded`, `program_ids`, `recognize` et `decode`. Un test d'intégration downstream-style l'implémente avec un type décodé tiers et un `Pubkey` explicitement absent du registry Core. L'implémentation ne requiert ni `ksp-program-lib`, ni enum centrale, ni `Any`, JSON ou codec.
|
||||||
|
|
||||||
### `pre.005` — Adversarial/API hardening + completeness
|
### `pre.005` — Adversarial/API hardening + completeness
|
||||||
|
|
||||||
@@ -620,7 +622,32 @@ registry / canonical payload / preparer absents
|
|||||||
normal dependencies inchangées : Core + Interface
|
normal dependencies inchangées : Core + Interface
|
||||||
```
|
```
|
||||||
|
|
||||||
Les tests unitaires vérifient notamment qu'un type décodé externe dépourvu de `Debug` peut être contenu et formaté via l'outcome sans exposer sa valeur. Le passage à `pre.004` reste conditionné au gate opérateur de cet overlay.
|
Les tests unitaires vérifient notamment qu'un type décodé externe dépourvu de `Debug` peut être contenu et formaté via l'outcome sans exposer sa valeur. Le gate opérateur `pre.003` fourni le 28 août 2026 est intégralement vert et autorise `pre.004`.
|
||||||
|
|
||||||
|
## 13.3 État préparé après `pre.004`
|
||||||
|
|
||||||
|
La tranche matérialise exactement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ProgramInstructionDecoder public depuis le crate-root
|
||||||
|
supertraits Send + Sync
|
||||||
|
associated output type Decoded possédé par l'implémentation
|
||||||
|
program_ids &[Pubkey] opaque/open-world
|
||||||
|
recognize &ProgramInstruction -> Recognition
|
||||||
|
decode &ProgramInstruction -> Result<Outcome<Self::Decoded>>
|
||||||
|
default methods aucun
|
||||||
|
external implementation canary présent comme crate d'intégration séparée
|
||||||
|
external decoded type défini hors code de production KSP
|
||||||
|
external Program Pubkey explicitement absent du registry Core
|
||||||
|
central Program enum / Any / JSON absents
|
||||||
|
registry dyn / descriptor / D3 payload absents
|
||||||
|
ProgramExecutionPreparer absent
|
||||||
|
normal dependencies inchangées : Core + Interface
|
||||||
|
```
|
||||||
|
|
||||||
|
Le canari externe utilise uniquement la façade `ksp_program_api::*` pour l'implémentation du trait ; l'accès direct à `ksp_core_lib::find_program_pubkey` est limité à l'assertion de test prouvant que le Program choisi n'est pas enregistré. Aucune API de registry n'est réexportée par Program API.
|
||||||
|
|
||||||
|
`pre.005` reste une tranche de hardening/completeness : elle ne doit pas élargir le contrat fonctionnel.
|
||||||
|
|
||||||
## 14. Hors périmètre confirmé
|
## 14. Hors périmètre confirmé
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/validation/017-V0_2_14_PROGRAM_API.md -->
|
<!-- file: docs/validation/017-V0_2_14_PROGRAM_API.md -->
|
||||||
<!-- version: 3 -->
|
<!-- version: 4 -->
|
||||||
|
|
||||||
# Validation `0.2.14` — Program API foundation
|
# Validation `0.2.14` — Program API foundation
|
||||||
|
|
||||||
@@ -40,10 +40,10 @@ Le scope validé par `pre.001` est une foundation instruction-only avec output a
|
|||||||
| `ProgramInstructionRecognition` | `NoMatch / ProgramMatch / ExactMatch`, non exhaustif | PASS `pre.003` |
|
| `ProgramInstructionRecognition` | `NoMatch / ProgramMatch / ExactMatch`, non exhaustif | PASS `pre.003` |
|
||||||
| `ProgramInstructionDecodeOutcome<Decoded>` | `Decoded(Decoded) / Unsupported`, non exhaustif | PASS `pre.003` |
|
| `ProgramInstructionDecodeOutcome<Decoded>` | `Decoded(Decoded) / Unsupported`, non exhaustif | PASS `pre.003` |
|
||||||
| decoder failure | `ksp_core_lib::Result`, aucun statut `Failed` parallèle | `pre.003` / `pre.004` |
|
| decoder failure | `ksp_core_lib::Result`, aucun statut `Failed` parallèle | `pre.003` / `pre.004` |
|
||||||
| `ProgramInstructionDecoder` | `Send + Sync`, associated `Decoded` | `pre.004` |
|
| `ProgramInstructionDecoder` | `Send + Sync`, associated `Decoded` | PASS `pre.004` |
|
||||||
| input | `&ProgramInstruction` | `pre.004` |
|
| input | `&ProgramInstruction` | PASS `pre.004` |
|
||||||
| Program IDs déclarés | `&[Pubkey]`, opaque et open-world | `pre.004` |
|
| Program IDs déclarés | `&[Pubkey]`, opaque et open-world | PASS `pre.004` |
|
||||||
| output | type concret de l'implémentation | `pre.004` |
|
| output | type concret de l'implémentation | PASS `pre.004` |
|
||||||
| registry dyn | absent | completeness `pre.005` |
|
| registry dyn | absent | completeness `pre.005` |
|
||||||
| identity/version/coverage | absents | completeness `pre.005` |
|
| identity/version/coverage | absents | completeness `pre.005` |
|
||||||
| Program Account/Event/ReturnData decoder | absents | completeness `pre.005` |
|
| Program Account/Event/ReturnData decoder | absents | completeness `pre.005` |
|
||||||
@@ -53,7 +53,7 @@ Le scope validé par `pre.001` est une foundation instruction-only avec output a
|
|||||||
|
|
||||||
## 4. External implementation canary
|
## 4. External implementation canary
|
||||||
|
|
||||||
La preuve finale doit utiliser une crate consommatrice séparée et vérifier :
|
La preuve matérialisée en `pre.004` utilise une crate d'intégration consommatrice séparée et vérifie :
|
||||||
|
|
||||||
```text
|
```text
|
||||||
implementation de ProgramInstructionDecoder
|
implementation de ProgramInstructionDecoder
|
||||||
@@ -66,7 +66,7 @@ aucun ksp-program-lib
|
|||||||
aucun module privé
|
aucun module privé
|
||||||
```
|
```
|
||||||
|
|
||||||
Ce canari remplace toute affirmation documentaire non exécutable d'extensibilité.
|
Ce canari remplace toute affirmation documentaire non exécutable d'extensibilité. Son gate opérateur reste à confirmer avant de déclarer la tranche close.
|
||||||
|
|
||||||
## 5. Dependency firewall cible
|
## 5. Dependency firewall cible
|
||||||
|
|
||||||
@@ -102,17 +102,17 @@ tracing
|
|||||||
|
|
||||||
| Gate | Attendu | Statut initial |
|
| Gate | Attendu | Statut initial |
|
||||||
|---------------------------|--------------------------------------------------------------------------|----------------|
|
|---------------------------|--------------------------------------------------------------------------|----------------|
|
||||||
| unknown Program Pubkey | utilisable sans registry Core | PENDING |
|
| unknown Program Pubkey | utilisable sans registry Core | PASS `pre.004` |
|
||||||
| max Interface input | decoder consomme l'input déjà borné sans nouvelle allocation obligatoire | PENDING |
|
| max Interface input | decoder consomme l'input déjà borné sans nouvelle allocation obligatoire | PENDING |
|
||||||
| malformed program payload | `Err` ou `Unsupported` selon contrat, sans payload dans l'erreur | PENDING |
|
| malformed program payload | `Err` ou `Unsupported` selon contrat, sans payload dans l'erreur | PENDING |
|
||||||
| Debug recognition | aucun payload | PASS |
|
| Debug recognition | aucun payload | PASS |
|
||||||
| Debug outcome | contenu `Decoded` non rendu automatiquement | PASS |
|
| Debug outcome | contenu `Decoded` non rendu automatiquement | PASS |
|
||||||
| default methods | aucun default method susceptible de masquer panic/policy | PENDING |
|
| default methods | aucun default method susceptible de masquer panic/policy | PASS `pre.004` |
|
||||||
| closed-world enum | aucun inventaire central de Program kinds | PENDING |
|
| closed-world enum | aucun inventaire central de Program kinds | PASS `pre.004` |
|
||||||
| serde accidental | aucune dependency/derive | PENDING |
|
| serde accidental | aucune dependency/derive | PASS `pre.004` |
|
||||||
| runtime creep | aucun réseau, wallet, store, logging ou UI | PENDING |
|
| runtime creep | aucun réseau, wallet, store, logging ou UI | PASS `pre.004` |
|
||||||
| external output | type tiers accepté sans `Any`/JSON central | PENDING |
|
| external output | type tiers accepté sans `Any`/JSON central | PASS `pre.004` |
|
||||||
| dyn claim | aucune assertion d'object-safety hétérogène dans cette release | PENDING |
|
| dyn claim | aucune assertion d'object-safety hétérogène dans cette release | PASS `pre.004` |
|
||||||
|
|
||||||
## 7. Gates de fermeture
|
## 7. Gates de fermeture
|
||||||
|
|
||||||
@@ -190,4 +190,39 @@ Ce gate autorise l'ouverture de `pre.003`.
|
|||||||
| registry / descriptor / payload D3 | ABSENT | hors scope maintenu |
|
| registry / descriptor / payload D3 | ABSENT | hors scope maintenu |
|
||||||
| serde / codec / logging / runtime | ABSENT | dependency firewall maintenu |
|
| serde / codec / logging / runtime | ABSENT | dependency firewall maintenu |
|
||||||
|
|
||||||
Les canaris unitaires et publics de `pre.003` doivent passer avant d'ouvrir `pre.004`. La preuve d'implémentation externe, le Program Pubkey open-world et l'associated output complet restent réservés au trait decoder de la tranche suivante.
|
Le gate opérateur `pre.003` est confirmé intégralement vert : audits Rust/Markdown, check, Clippy, tests ciblés, workspace complet et graphes Cargo passent.
|
||||||
|
|
||||||
|
## 10. Gate opérateur `pre.003`
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all PASS
|
||||||
|
audit Rust général / exports / workspace PASS
|
||||||
|
audit Markdown PASS — 171 tables / 120 fichiers
|
||||||
|
cargo check --workspace PASS
|
||||||
|
cargo clippy --workspace --all-targets PASS
|
||||||
|
cargo test -p ksp-program-api PASS — 8 tests Rust
|
||||||
|
cargo test --workspace PASS
|
||||||
|
cargo tree -p ksp-program-api --edges normal PASS — Core + Interface uniquement
|
||||||
|
cargo tree --duplicates exécuté, inventaire workspace observé
|
||||||
|
```
|
||||||
|
|
||||||
|
Ce gate autorise l'ouverture de `pre.004`.
|
||||||
|
|
||||||
|
## 11. État préparé `pre.004`
|
||||||
|
|
||||||
|
| Critère | Statut | Preuve |
|
||||||
|
|------------------------------------|--------|----------------------------------------------------------------------|
|
||||||
|
| `ProgramInstructionDecoder` | PASS | trait public `Send + Sync` |
|
||||||
|
| associated `Decoded` | PASS | type sans bound imposé, possédé par l'implémentation |
|
||||||
|
| `program_ids` | PASS | `&[Pubkey]`, aucune validation registry dans l'API |
|
||||||
|
| `recognize` | PASS | input `&ProgramInstruction`, outcome `ProgramInstructionRecognition` |
|
||||||
|
| `decode` | PASS | `Result<ProgramInstructionDecodeOutcome<Self::Decoded>>` |
|
||||||
|
| default methods | ABSENT | toutes les méthodes sont obligatoires |
|
||||||
|
| external implementation canary | PASS | test d'intégration downstream-style séparé |
|
||||||
|
| Program Pubkey non enregistré | PASS | assertion `ksp_core_lib::find_program_pubkey(...) == None` |
|
||||||
|
| output externe concret | PASS | `ExternalDecodedInstruction` défini dans le consumer canary |
|
||||||
|
| `ksp-program-lib` | ABSENT | aucune dépendance ni implémentation officielle |
|
||||||
|
| registry / descriptor / payload D3 | ABSENT | hors scope maintenu |
|
||||||
|
| serde / codec / logging / runtime | ABSENT | dependency firewall inchangé |
|
||||||
|
|
||||||
|
Le canari d'implémentation consomme la façade `ksp_program_api` pour le trait, les types et les outcomes. Le registry Core n'est utilisé que par l'assertion de test négative et n'est pas réexporté par Program API. Le gate opérateur de cet overlay reste requis avant `pre.005`.
|
||||||
|
|||||||
Reference in New Issue
Block a user