v0.2.14-pre.002

This commit is contained in:
2026-08-28 11:07:16 +02:00
parent 5e5ed6d1ab
commit c1f61a380f
10 changed files with 605 additions and 9 deletions

View File

@@ -0,0 +1,15 @@
# file: crates/ksp-program-api/Cargo.toml
# version: 1
[package]
name = "ksp-program-api"
version.workspace = true
edition.workspace = true
repository.workspace = true
[dependencies]
ksp-core-lib = { path = "../ksp-core-lib" }
ksp-interface-lib = { path = "../ksp-interface-lib" }
[lints]
workspace = true

View File

@@ -0,0 +1,92 @@
<!-- file: crates/ksp-program-api/README.md -->
<!-- version: 1 -->
# 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é.
## Ownership
La crate dépend uniquement de :
```text
ksp-program-api
├── ksp-core-lib
└── ksp-interface-lib
└── ksp-core-lib
```
Core reste propriétaire de :
```text
Error
ErrorCode
ErrorContext
Result
Pubkey
```
Interface reste propriétaire de :
```text
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.
## Surface de scaffold
La façade `pre.002` expose exactement :
```text
Error
ErrorCode
ErrorContext
Result
Pubkey
ProgramAccountMeta
ProgramInstruction
```
Aucun module interne n'est public.
Les contrats suivants restent réservés aux tranches suivantes :
```text
ProgramInstructionRecognition pre.003
ProgramInstructionDecodeOutcome<T> pre.003
ProgramInstructionDecoder pre.004
```
## Frontières
La foundation ne contient pas :
```text
ksp-program-lib
registry runtime
identity/version/coverage de decoder
payload canonique D3
ProgramAccountDecoder
ProgramEventDecoder
ProgramReturnDataDecoder
ProgramExecutionPreparer
serde / serde_json
borsh / wincode / bincode
solana-instruction
network / async runtime
logging / tracing
Wallet / Transport / Store / Materializer / Config / Tauri
```
L'absence de ces surfaces est volontaire : `ksp-program-api` reste une API déclarative, ouverte et indépendante des implémentations/runtime supérieurs.
## Références
- [Usage public](USAGE.md)
- [Plan `0.2.14`](../../docs/plans/021-V0_2_14_PROGRAM_API_PLAN.md)
- [Validation `0.2.14`](../../docs/validation/017-V0_2_14_PROGRAM_API.md)
- [Architecture Wire + Program](../../docs/architecture/006-WIRE_AND_PROGRAM.md)

View File

@@ -0,0 +1,55 @@
<!-- file: crates/ksp-program-api/USAGE.md -->
<!-- version: 1 -->
# 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.
## Construire un input Program avec la façade
```rust
let program_id = ksp_program_api::Pubkey::new_from_array([1_u8; 32]);
let account_id = ksp_program_api::Pubkey::new_from_array([2_u8; 32]);
let account = ksp_program_api::ProgramAccountMeta::readonly(account_id, true);
let instruction = ksp_program_api::ProgramInstruction::try_new(
program_id,
std::vec![account],
std::vec![0x01_u8, 0x02, 0x03],
);
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.
## Utiliser le contrat d'erreur commun
Les types d'erreur Core sont également disponibles depuis la façade :
```rust
fn forward_result(
value: ksp_program_api::Result<ksp_program_api::ProgramInstruction>,
) -> ksp_program_api::Result<ksp_program_api::ProgramInstruction> {
return value;
}
```
Aucun type d'erreur Program spécifique n'est nécessaire au scaffold.
## Ce que `pre.002` ne fournit pas
Il n'existe encore aucun :
```text
recognize(...)
decode(...)
ProgramInstructionRecognition
ProgramInstructionDecodeOutcome
ProgramInstructionDecoder
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.

View File

@@ -0,0 +1,28 @@
// file: crates/ksp-program-api/src/lib.rs
// version: 1
#![warn(missing_docs)]
#![deny(unreachable_pub)]
#![forbid(unsafe_code)]
//! 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.
/// 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.
pub use ksp_core_lib::ErrorCode;
/// Structured contextual field attached to a KSP error.
pub use ksp_core_lib::ErrorContext;
/// Canonical Solana account address primitive owned by `ksp-core-lib`.
pub use ksp_core_lib::Pubkey;
/// Common KSP result alias using [`Error`].
pub use ksp_core_lib::Result;
/// Passive account metadata attached to one Program instruction.
pub use ksp_interface_lib::ProgramAccountMeta;
/// Passive, bounded Program instruction wire contract.
pub use ksp_interface_lib::ProgramInstruction;

View File

@@ -0,0 +1,91 @@
// file: crates/ksp-program-api/tests/dependency_boundary.rs
// version: 1
//! Dependency and declarative-surface canaries for the Program API scaffold.
#[test]
fn pre_002_manifest_has_exact_core_and_interface_runtime_dependencies() {
let manifest = include_str!("../Cargo.toml");
let dependencies_tail = manifest.split("[dependencies]").nth(1);
assert!(dependencies_tail.is_some(), "Program API dependencies section must exist");
let dependencies_tail = match dependencies_tail {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
let dependencies = match dependencies_tail.split("[lints]").next() {
std::option::Option::Some(value) => value,
std::option::Option::None => return,
};
assert_eq!(manifest_dependency_names(dependencies), std::vec!["ksp-core-lib", "ksp-interface-lib"]);
for forbidden in [
"ksp-config-lib",
"ksp-logging-lib",
"ksp-materializer-api",
"ksp-materializer-lib",
"ksp-offchain-transport-lib",
"ksp-onchain-transport-lib",
"ksp-program-lib",
"ksp-store-api",
"ksp-store-lib",
"ksp-wallet-lib",
"borsh",
"bincode",
"reqwest",
"serde",
"serde_json",
"solana-instruction",
"tauri",
"tokio",
"tonic",
"tracing",
"wincode",
] {
assert!(!dependencies.contains(forbidden), "forbidden Program API dependency detected: {forbidden}");
}
return;
}
#[test]
fn pre_002_crate_root_is_facade_only_without_decoder_runtime_surface() {
let crate_root = include_str!("../src/lib.rs");
for required in ["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}");
}
assert!(!std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src/constants.rs").exists());
return;
}
fn manifest_dependency_names(section: &str) -> std::vec::Vec<&str> {
let mut names = std::vec::Vec::new();
for line in section.lines() {
let content = match line.split('#').next() {
std::option::Option::Some(value) => value.trim(),
std::option::Option::None => continue,
};
if content.is_empty() {
continue;
}
let name = match content.split('=').next() {
std::option::Option::Some(value) => value.trim().trim_end_matches(".workspace"),
std::option::Option::None => continue,
};
if !name.is_empty() {
names.push(name);
}
}
names.sort_unstable();
return names;
}

View File

@@ -0,0 +1,33 @@
// file: crates/ksp-program-api/tests/public_api.rs
// version: 1
//! Integration canaries for the public `ksp-program-api` scaffold.
fn consume_result(value: ksp_program_api::Result<ksp_program_api::Pubkey>) -> ksp_program_api::Result<ksp_program_api::Pubkey> {
return value;
}
#[test]
fn public_pre_002_core_and_interface_facade_is_available_from_crate_root() {
let program_id = ksp_program_api::Pubkey::new_from_array([0xA1_u8; 32]);
let account_id = ksp_program_api::Pubkey::new_from_array([0xA2_u8; 32]);
let account = ksp_program_api::ProgramAccountMeta::readonly(account_id, true);
let instruction = ksp_program_api::ProgramInstruction::try_new(program_id, std::vec![account], std::vec![0xA3_u8]);
assert!(instruction.is_ok());
let forwarded = consume_result(std::result::Result::Ok(program_id));
assert!(forwarded.is_ok());
let error_code_type: std::option::Option<ksp_program_api::ErrorCode> = std::option::Option::None;
let error_context_type: std::option::Option<ksp_program_api::ErrorContext> = std::option::Option::None;
let error_type: std::option::Option<ksp_program_api::Error> = std::option::Option::None;
assert!(error_code_type.is_none());
assert!(error_context_type.is_none());
assert!(error_type.is_none());
return;
}
#[test]
fn public_pre_002_scaffold_does_not_require_private_modules() {
let source = include_str!("../src/lib.rs");
assert!(!source.contains("pub mod "));
return;
}