v0.2.13-pre.005
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
# file: Cargo.toml
|
# file: Cargo.toml
|
||||||
# version: 306
|
# version: 307
|
||||||
|
|
||||||
[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-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-wallet-lib"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.2.13-pre.4"
|
version = "0.2.13-pre.5"
|
||||||
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,5 +1,5 @@
|
|||||||
// file: crates/ksp-interface-lib/tests/dependency_boundary.rs
|
// file: crates/ksp-interface-lib/tests/dependency_boundary.rs
|
||||||
// version: 3
|
// version: 4
|
||||||
|
|
||||||
//! Dependency and passive-surface canaries for the Interface foundation.
|
//! Dependency and passive-surface canaries for the Interface foundation.
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ fn pre_002_manifest_has_exact_core_only_runtime_dependency() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pre_004_surface_remains_passive_without_codecs_or_runtime_logging() {
|
fn pre_005_surface_remains_passive_without_codecs_or_runtime_logging() {
|
||||||
let crate_root = include_str!("../src/lib.rs");
|
let crate_root = include_str!("../src/lib.rs");
|
||||||
assert!(crate_root.contains("ProgramAccountMeta"));
|
assert!(crate_root.contains("ProgramAccountMeta"));
|
||||||
assert!(crate_root.contains("MAX_PROGRAM_INSTRUCTION_ACCOUNTS"));
|
assert!(crate_root.contains("MAX_PROGRAM_INSTRUCTION_ACCOUNTS"));
|
||||||
@@ -83,3 +83,49 @@ fn manifest_dependency_names(section: &str) -> std::vec::Vec<&str> {
|
|||||||
names.sort_unstable();
|
names.sort_unstable();
|
||||||
return names;
|
return names;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_005_all_production_sources_preserve_the_dependency_firewall() {
|
||||||
|
let production_sources = [
|
||||||
|
include_str!("../src/error.rs"),
|
||||||
|
include_str!("../src/lib.rs"),
|
||||||
|
include_str!("../src/program_account_meta.rs"),
|
||||||
|
include_str!("../src/program_instruction.rs"),
|
||||||
|
];
|
||||||
|
for source in production_sources {
|
||||||
|
for forbidden in [
|
||||||
|
"borsh::",
|
||||||
|
"bincode::",
|
||||||
|
"ksp_config_lib::",
|
||||||
|
"ksp_logging_lib::",
|
||||||
|
"ksp_offchain_transport_lib::",
|
||||||
|
"ksp_onchain_transport_lib::",
|
||||||
|
"ksp_program_api::",
|
||||||
|
"ksp_program_lib::",
|
||||||
|
"ksp_store_api::",
|
||||||
|
"ksp_store_lib::",
|
||||||
|
"ksp_wallet_lib::",
|
||||||
|
"reqwest::",
|
||||||
|
"serde::",
|
||||||
|
"serde_json::",
|
||||||
|
"solana_instruction::",
|
||||||
|
"tauri::",
|
||||||
|
"tokio::",
|
||||||
|
"tonic::",
|
||||||
|
"tracing::",
|
||||||
|
"wincode::",
|
||||||
|
] {
|
||||||
|
assert!(!source.contains(forbidden), "forbidden production dependency path detected: {forbidden}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_005_instruction_source_has_no_narrowing_cast_or_hidden_codec_entry_point() {
|
||||||
|
let instruction_source = include_str!("../src/program_instruction.rs");
|
||||||
|
for forbidden in [" as u8", " as u16", " as u32", " as u64", "serialize", "deserialize", "encode", "decode"] {
|
||||||
|
assert!(!instruction_source.contains(forbidden), "forbidden instruction implementation pattern detected: {forbidden}");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|||||||
27
crates/ksp-interface-lib/tests/external_consumer.rs
Normal file
27
crates/ksp-interface-lib/tests/external_consumer.rs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// file: crates/ksp-interface-lib/tests/external_consumer.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
//! Downstream-style consumer canary for the public Interface facade.
|
||||||
|
|
||||||
|
fn consume_instruction(instruction: ksp_interface_lib::ProgramInstruction) -> (ksp_interface_lib::Pubkey, usize, usize) {
|
||||||
|
return (*instruction.program_id(), instruction.accounts().len(), instruction.data().len());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_005_external_consumer_uses_only_the_crate_root_facade() {
|
||||||
|
let program_id = ksp_interface_lib::Pubkey::new_from_array([0xA1_u8; 32]);
|
||||||
|
let account_id = ksp_interface_lib::Pubkey::new_from_array([0xA2_u8; 32]);
|
||||||
|
let readonly = ksp_interface_lib::ProgramAccountMeta::readonly(account_id, true);
|
||||||
|
let writable = ksp_interface_lib::ProgramAccountMeta::writable(account_id, false);
|
||||||
|
let instruction = ksp_interface_lib::ProgramInstruction::try_new(program_id, std::vec![readonly, writable, readonly], std::vec![7_u8, 8, 9]);
|
||||||
|
assert!(instruction.is_ok());
|
||||||
|
let instruction = match instruction {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(consume_instruction(instruction), (program_id, 3, 3));
|
||||||
|
assert_eq!(ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_ACCOUNTS, 255);
|
||||||
|
assert_eq!(ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_DATA_LEN, 10_240);
|
||||||
|
assert_eq!(ksp_interface_lib::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED.domain(), "interface");
|
||||||
|
return;
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-interface-lib/tests/public_api.rs
|
// file: crates/ksp-interface-lib/tests/public_api.rs
|
||||||
// version: 3
|
// version: 4
|
||||||
|
|
||||||
//! Integration canaries for the public `ksp-interface-lib` foundation.
|
//! Integration canaries for the public `ksp-interface-lib` foundation.
|
||||||
|
|
||||||
@@ -53,3 +53,27 @@ fn public_pre_004_program_instruction_contract_is_available_from_crate_root() {
|
|||||||
assert_eq!(ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_DATA_LEN, 10_240);
|
assert_eq!(ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_DATA_LEN, 10_240);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn public_pre_005_limit_error_is_observable_without_private_module_access() {
|
||||||
|
let program_id = ksp_interface_lib::Pubkey::new_from_array([0xC1_u8; 32]);
|
||||||
|
let rejected = ksp_interface_lib::ProgramInstruction::try_new(
|
||||||
|
program_id,
|
||||||
|
std::vec::Vec::new(),
|
||||||
|
std::vec![0xC2_u8; ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_DATA_LEN + 1],
|
||||||
|
);
|
||||||
|
assert!(rejected.is_err());
|
||||||
|
let error = match rejected {
|
||||||
|
std::result::Result::Err(value) => value,
|
||||||
|
std::result::Result::Ok(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(error.code(), ksp_interface_lib::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED);
|
||||||
|
assert_eq!(error.context().len(), 3);
|
||||||
|
assert_eq!(error.context()[0].key(), "field");
|
||||||
|
assert_eq!(error.context()[0].value(), "data");
|
||||||
|
assert_eq!(error.context()[1].key(), "actual_len");
|
||||||
|
assert_eq!(error.context()[1].value(), "10241");
|
||||||
|
assert_eq!(error.context()[2].key(), "maximum_len");
|
||||||
|
assert_eq!(error.context()[2].value(), "10240");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|||||||
75
crates/ksp-interface-lib/tests/release_completeness.rs
Normal file
75
crates/ksp-interface-lib/tests/release_completeness.rs
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
// file: crates/ksp-interface-lib/tests/release_completeness.rs
|
||||||
|
// version: 1
|
||||||
|
|
||||||
|
//! Release-level completeness canaries for the `0.2.13` Interface foundation.
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_005_exact_crate_root_export_inventory_is_stable() {
|
||||||
|
let crate_root = include_str!("../src/lib.rs");
|
||||||
|
let mut actual = std::vec::Vec::new();
|
||||||
|
for line in crate_root.lines() {
|
||||||
|
let trimmed = line.trim();
|
||||||
|
if trimmed.starts_with("pub use ") {
|
||||||
|
actual.push(trimmed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
actual.sort_unstable();
|
||||||
|
let mut expected = std::vec![
|
||||||
|
"pub use self::error::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED;",
|
||||||
|
"pub use self::program_account_meta::MAX_PROGRAM_INSTRUCTION_ACCOUNTS;",
|
||||||
|
"pub use self::program_account_meta::ProgramAccountMeta;",
|
||||||
|
"pub use self::program_instruction::MAX_PROGRAM_INSTRUCTION_DATA_LEN;",
|
||||||
|
"pub use self::program_instruction::ProgramInstruction;",
|
||||||
|
"pub use ksp_core_lib::Pubkey;",
|
||||||
|
];
|
||||||
|
expected.sort_unstable();
|
||||||
|
assert_eq!(actual, expected);
|
||||||
|
assert!(!crate_root.contains("pub mod "));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_005_production_module_inventory_contains_no_second_wire_domain() -> std::io::Result<()> {
|
||||||
|
let source_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
|
||||||
|
let entries = match std::fs::read_dir(source_root) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let mut names = std::vec::Vec::new();
|
||||||
|
for entry in entries {
|
||||||
|
let entry = match entry {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
let file_type = match entry.file_type() {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
|
};
|
||||||
|
if !file_type.is_file() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let name = match entry.file_name().into_string() {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => continue,
|
||||||
|
};
|
||||||
|
if name.ends_with(".rs") {
|
||||||
|
names.push(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
names.sort_unstable();
|
||||||
|
assert_eq!(names, std::vec!["error.rs", "lib.rs", "program_account_meta.rs", "program_instruction.rs"]);
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_005_foundation_has_one_error_code_and_two_bounded_passive_types() {
|
||||||
|
assert_eq!(ksp_interface_lib::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED.domain(), "interface");
|
||||||
|
assert_eq!(ksp_interface_lib::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED.code(), "program_instruction_limit_exceeded");
|
||||||
|
assert_eq!(ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_ACCOUNTS, 255);
|
||||||
|
assert_eq!(ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_DATA_LEN, 10_240);
|
||||||
|
let program_id = ksp_interface_lib::Pubkey::new_from_array([0xB1_u8; 32]);
|
||||||
|
let account = ksp_interface_lib::ProgramAccountMeta::readonly(ksp_interface_lib::Pubkey::new_from_array([0xB2_u8; 32]), false);
|
||||||
|
let instruction = ksp_interface_lib::ProgramInstruction::try_new(program_id, std::vec![account], std::vec![0xB3_u8]);
|
||||||
|
assert!(instruction.is_ok());
|
||||||
|
return;
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-interface-lib/unit_tests/program_instruction.rs
|
// file: crates/ksp-interface-lib/unit_tests/program_instruction.rs
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
fn meta(byte: u8, writable: bool) -> crate::ProgramAccountMeta {
|
fn meta(byte: u8, writable: bool) -> crate::ProgramAccountMeta {
|
||||||
let pubkey = crate::Pubkey::new_from_array([byte; 32]);
|
let pubkey = crate::Pubkey::new_from_array([byte; 32]);
|
||||||
@@ -105,3 +105,61 @@ fn instruction_debug_is_bounded_and_omits_accounts_and_payload_bytes() {
|
|||||||
assert!(!rendered.contains("is_writable"));
|
assert!(!rendered.contains("is_writable"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn instruction_preserves_owned_vector_allocations_without_internal_reallocation() {
|
||||||
|
let program_id = crate::Pubkey::new_from_array([0x81_u8; 32]);
|
||||||
|
let accounts = std::vec![meta(10, false), meta(11, true), meta(12, false)];
|
||||||
|
let accounts_ptr = accounts.as_ptr();
|
||||||
|
let data = std::vec![0x10_u8, 0x20, 0x30, 0x40];
|
||||||
|
let data_ptr = data.as_ptr();
|
||||||
|
let instruction = crate::ProgramInstruction::try_new(program_id, accounts, data);
|
||||||
|
assert!(instruction.is_ok());
|
||||||
|
let instruction = match instruction {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return,
|
||||||
|
};
|
||||||
|
assert_eq!(instruction.accounts().as_ptr(), accounts_ptr);
|
||||||
|
assert_eq!(instruction.data().as_ptr(), data_ptr);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn instruction_limit_errors_do_not_echo_hostile_payload_or_account_material() {
|
||||||
|
let program_id = crate::Pubkey::new_from_array([0x91_u8; 32]);
|
||||||
|
let hostile_marker = b"HOSTILE_INTERFACE_PAYLOAD_SENTINEL";
|
||||||
|
let mut hostile_data = hostile_marker.to_vec();
|
||||||
|
hostile_data.resize(crate::MAX_PROGRAM_INSTRUCTION_DATA_LEN + 1, 0xA5_u8);
|
||||||
|
let data_error = crate::ProgramInstruction::try_new(program_id, std::vec::Vec::new(), hostile_data);
|
||||||
|
assert!(data_error.is_err());
|
||||||
|
let data_error = match data_error {
|
||||||
|
std::result::Result::Err(value) => value,
|
||||||
|
std::result::Result::Ok(_) => return,
|
||||||
|
};
|
||||||
|
let data_display = std::format!("{data_error}");
|
||||||
|
let data_debug = std::format!("{data_error:?}");
|
||||||
|
assert!(!data_display.contains("HOSTILE_INTERFACE_PAYLOAD_SENTINEL"));
|
||||||
|
assert!(!data_debug.contains("HOSTILE_INTERFACE_PAYLOAD_SENTINEL"));
|
||||||
|
for context in data_error.context() {
|
||||||
|
assert!(!context.value().contains("HOSTILE_INTERFACE_PAYLOAD_SENTINEL"));
|
||||||
|
}
|
||||||
|
let hostile_account = meta(0xE1, true);
|
||||||
|
let hostile_account_debug = std::format!("{hostile_account:?}");
|
||||||
|
let account_error = crate::ProgramInstruction::try_new(
|
||||||
|
program_id,
|
||||||
|
std::vec![hostile_account; crate::MAX_PROGRAM_INSTRUCTION_ACCOUNTS + 1],
|
||||||
|
std::vec::Vec::new(),
|
||||||
|
);
|
||||||
|
assert!(account_error.is_err());
|
||||||
|
let account_error = match account_error {
|
||||||
|
std::result::Result::Err(value) => value,
|
||||||
|
std::result::Result::Ok(_) => return,
|
||||||
|
};
|
||||||
|
let account_debug = std::format!("{account_error:?}");
|
||||||
|
assert!(!account_debug.contains(hostile_account_debug.as_str()));
|
||||||
|
assert_eq!(account_error.context().len(), 3);
|
||||||
|
assert_eq!(account_error.context()[0].value(), "accounts");
|
||||||
|
assert_eq!(account_error.context()[1].value(), "256");
|
||||||
|
assert_eq!(account_error.context()[2].value(), "255");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|||||||
213
deltas/0.2.13/pre.005.md
Normal file
213
deltas/0.2.13/pre.005.md
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
<!-- file: deltas/0.2.13/pre.005.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.2.13-pre.005` — hardening adversarial + consumer externe + complétude
|
||||||
|
|
||||||
|
## 1. Base requise
|
||||||
|
|
||||||
|
Cette tranche s'applique exclusivement sur :
|
||||||
|
|
||||||
|
```text
|
||||||
|
v0.2.12
|
||||||
|
+ 0.2.13-pre.001
|
||||||
|
+ 0.2.13-pre.002
|
||||||
|
+ 0.2.13-pre.003
|
||||||
|
+ 0.2.13-pre.004
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur fourni pour `pre.004` est intégralement vert :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all PASS
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py PASS
|
||||||
|
python3 scripts/audit_markdown_tables.py ... PASS
|
||||||
|
cargo check --workspace PASS
|
||||||
|
cargo clippy --workspace --all-targets PASS
|
||||||
|
cargo test -p ksp-interface-lib PASS
|
||||||
|
cargo test --workspace PASS
|
||||||
|
cargo tree -p ksp-interface-lib --edges normal inspecté
|
||||||
|
cargo tree --duplicates inspecté
|
||||||
|
```
|
||||||
|
|
||||||
|
Le graphe ciblé reste strictement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-interface-lib
|
||||||
|
└── ksp-core-lib
|
||||||
|
└── solana-pubkey 4.3.0
|
||||||
|
```
|
||||||
|
|
||||||
|
La version workspace passe de :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.13-pre.4
|
||||||
|
```
|
||||||
|
|
||||||
|
à :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.2.13-pre.5
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Objectif
|
||||||
|
|
||||||
|
Fermer le hardening du premier lot wire générique sans ajouter de capacité fonctionnelle ni de second domaine wire.
|
||||||
|
|
||||||
|
La tranche ajoute uniquement des preuves :
|
||||||
|
|
||||||
|
```text
|
||||||
|
consumer externe crate-root
|
||||||
|
inventaire exact de complétude release
|
||||||
|
firewall source/manifest renforcé
|
||||||
|
absence de narrowing/codec caché
|
||||||
|
transfert sans réallocation des Vec admis
|
||||||
|
absence de contenu hostile dans les erreurs de limite
|
||||||
|
public API boundary canary
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun fichier `src/` de production n'est modifié.
|
||||||
|
|
||||||
|
## 3. Consumer externe
|
||||||
|
|
||||||
|
`tests/external_consumer.rs` constitue le canari downstream. Comme tout integration test Rust, il est compilé comme un crate distinct de la bibliothèque et utilise exclusivement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp_interface_lib::Pubkey
|
||||||
|
ksp_interface_lib::ProgramAccountMeta
|
||||||
|
ksp_interface_lib::ProgramInstruction
|
||||||
|
ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_ACCOUNTS
|
||||||
|
ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_DATA_LEN
|
||||||
|
ksp_interface_lib::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun chemin vers un module privé n'est requis.
|
||||||
|
|
||||||
|
## 4. Complétude de release
|
||||||
|
|
||||||
|
`tests/release_completeness.rs` verrouille exactement les six exports crate-root retenus pour `0.2.13` et refuse une façade `pub mod`.
|
||||||
|
|
||||||
|
Il verrouille également l'inventaire des fichiers Rust de production :
|
||||||
|
|
||||||
|
```text
|
||||||
|
error.rs
|
||||||
|
lib.rs
|
||||||
|
program_account_meta.rs
|
||||||
|
program_instruction.rs
|
||||||
|
```
|
||||||
|
|
||||||
|
Ce canari rend explicite qu'un second lot wire, un codec générique, un runtime ou un nouveau domaine ne peut pas être ajouté silencieusement dans cette release.
|
||||||
|
|
||||||
|
## 5. Hardening adversarial
|
||||||
|
|
||||||
|
Les unit tests de `ProgramInstruction` sont renforcés sur deux propriétés.
|
||||||
|
|
||||||
|
Premièrement, les pointeurs des `Vec<ProgramAccountMeta>` et `Vec<u8>` sont capturés avant `try_new` puis comparés aux slices exposés après construction. Le canari prouve que l'implémentation courante transfère les allocations admises sans clone/réallocation interne.
|
||||||
|
|
||||||
|
Deuxièmement, les deux branches d'erreur de limite sont confrontées à du matériel hostile : payload avec marqueur explicite et account meta arbitraire. Les rendus/contextes d'erreur doivent rester bornés aux champs sûrs déjà définis :
|
||||||
|
|
||||||
|
```text
|
||||||
|
field
|
||||||
|
actual_len
|
||||||
|
maximum_len
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun payload ni account meta n'est recopié dans l'erreur.
|
||||||
|
|
||||||
|
## 6. Dependency/API firewall
|
||||||
|
|
||||||
|
`tests/dependency_boundary.rs` conserve l'exigence manifeste :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-core-lib = seule dépendance normale
|
||||||
|
```
|
||||||
|
|
||||||
|
et étend le scan à tous les fichiers Rust de production pour interdire les chemins directs vers :
|
||||||
|
|
||||||
|
```text
|
||||||
|
Program/Store/Transport/Config/Wallet
|
||||||
|
serde/serde_json
|
||||||
|
borsh/bincode/wincode
|
||||||
|
solana-instruction
|
||||||
|
reqwest/tokio/tonic/tauri
|
||||||
|
tracing/ksp-logging-lib
|
||||||
|
```
|
||||||
|
|
||||||
|
Un canari dédié refuse également les casts étroits et les points d'entrée génériques `serialize/deserialize/encode/decode` dans `program_instruction.rs`.
|
||||||
|
|
||||||
|
## 7. Public API
|
||||||
|
|
||||||
|
`tests/public_api.rs` ajoute un test de rejet `10_241` bytes depuis un consumer externe au module privé et vérifie que le caller reçoit le code Core commun ainsi que les trois contextes sûrs attendus.
|
||||||
|
|
||||||
|
Aucune nouvelle API publique n'est créée par `pre.005`.
|
||||||
|
|
||||||
|
## 8. Logging et codecs
|
||||||
|
|
||||||
|
La décision reste inchangée : Interface est passive.
|
||||||
|
|
||||||
|
Toujours absents :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-logging-lib
|
||||||
|
constants.rs
|
||||||
|
TRACING_TARGET
|
||||||
|
tracing direct
|
||||||
|
serde
|
||||||
|
borsh
|
||||||
|
bincode
|
||||||
|
wincode
|
||||||
|
solana-instruction
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-interface-lib/tests/external_consumer.rs
|
||||||
|
crates/ksp-interface-lib/tests/release_completeness.rs
|
||||||
|
deltas/0.2.13/pre.005.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 10. Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-interface-lib/tests/dependency_boundary.rs
|
||||||
|
crates/ksp-interface-lib/tests/public_api.rs
|
||||||
|
crates/ksp-interface-lib/unit_tests/program_instruction.rs
|
||||||
|
docs/plans/020-V0_2_13_INTERFACE_PLAN.md
|
||||||
|
docs/validation/016-V0_2_13_INTERFACE.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 11. Fichiers volontairement inchangés
|
||||||
|
|
||||||
|
```text
|
||||||
|
README.md
|
||||||
|
ROADMAP.md
|
||||||
|
CHANGELOG.md
|
||||||
|
.env.example
|
||||||
|
crates/ksp-interface-lib/Cargo.toml
|
||||||
|
crates/ksp-interface-lib/README.md
|
||||||
|
crates/ksp-interface-lib/USAGE.md
|
||||||
|
crates/ksp-interface-lib/src/**
|
||||||
|
crates/ksp-interface-lib/unit_tests/program_account_meta.rs
|
||||||
|
docs/architecture/**
|
||||||
|
prompts/**
|
||||||
|
config/**
|
||||||
|
```
|
||||||
|
|
||||||
|
## 12. Gate attendu
|
||||||
|
|
||||||
|
Après application de l'overlay :
|
||||||
|
|
||||||
|
```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.13
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-interface-lib
|
||||||
|
cargo test --workspace
|
||||||
|
cargo tree -p ksp-interface-lib --edges normal
|
||||||
|
cargo tree --duplicates
|
||||||
|
```
|
||||||
|
|
||||||
|
Si ce gate est vert, `pre.006` doit rester un gate technique final sans nouveau développement fonctionnel.
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/plans/020-V0_2_13_INTERFACE_PLAN.md -->
|
<!-- file: docs/plans/020-V0_2_13_INTERFACE_PLAN.md -->
|
||||||
<!-- version: 4 -->
|
<!-- version: 5 -->
|
||||||
|
|
||||||
# Plan `0.2.13` — Interface / wire foundation
|
# Plan `0.2.13` — Interface / wire foundation
|
||||||
|
|
||||||
@@ -395,13 +395,13 @@ Matérialiser la primitive de compte ordonné, la borne accounts, le modèle d'e
|
|||||||
|
|
||||||
### pre.004 — `ProgramInstruction` passif borné
|
### pre.004 — `ProgramInstruction` passif borné
|
||||||
|
|
||||||
**Statut : réalisé ; gate opérateur à confirmer.**
|
**Statut : réalisé ; gate opérateur intégralement PASS.**
|
||||||
|
|
||||||
Ajouter l'instruction `{ program_id, accounts, data }`, constructeur validé, accessors, Debug résumé et canaris d'ordre/doublons/limites. Aucun serde/codec ou comportement Program.
|
Ajouter l'instruction `{ program_id, accounts, data }`, constructeur validé, accessors, Debug résumé et canaris d'ordre/doublons/limites. Aucun serde/codec ou comportement Program.
|
||||||
|
|
||||||
### pre.005 — adversarial + consumer externe + API/dependency hardening
|
### pre.005 — adversarial + consumer externe + API/dependency hardening
|
||||||
|
|
||||||
**Statut : prévu**
|
**Statut : réalisé ; gate opérateur à confirmer.**
|
||||||
|
|
||||||
Fermer les cas limites, surface crate-root, consumer externe, firewall source/manifest et graphes Cargo. Ne pas ajouter un second domaine wire opportuniste.
|
Fermer les cas limites, surface crate-root, consumer externe, firewall source/manifest et graphes Cargo. Ne pas ajouter un second domaine wire opportuniste.
|
||||||
|
|
||||||
@@ -534,4 +534,43 @@ Les deux limites utilisent le code commun `interface.program_instruction_limit_e
|
|||||||
|
|
||||||
Le `Debug` manuel n'imprime ni la collection accounts ni les octets `data`. Il expose uniquement l'identité publique du programme et les deux longueurs structurelles utiles au diagnostic.
|
Le `Debug` manuel n'imprime ni la collection accounts ni les octets `data`. Il expose uniquement l'identité publique du programme et les deux longueurs structurelles utiles au diagnostic.
|
||||||
|
|
||||||
Le passage à `pre.005` doit désormais se concentrer sur les canaris adversariaux/consumer externe/API/dependency hardening, sans ajouter un second domaine wire.
|
Le gate opérateur `pre.004` confirme désormais fmt, audits Rust/Markdown, `cargo check --workspace`, Clippy, tests ciblés Interface, `cargo test --workspace` et les deux graphes Cargo. Le passage à `pre.005` se concentre donc exclusivement sur les canaris adversariaux/consumer externe/API/dependency hardening, sans ajouter un second domaine wire.
|
||||||
|
|
||||||
|
## 19. État préparé `pre.005`
|
||||||
|
|
||||||
|
`pre.005` n'ajoute aucun code de production et ferme uniquement les preuves de consommation, complétude et résistance adversariale autour de la surface déjà matérialisée :
|
||||||
|
|
||||||
|
```text
|
||||||
|
production src modifié non
|
||||||
|
nouveau domaine wire non
|
||||||
|
external consumer integration crate présent
|
||||||
|
release completeness inventory présent
|
||||||
|
façade crate-root exacte 6 exports contrôlés
|
||||||
|
modules de production 4 fichiers exacts
|
||||||
|
Vec accounts/data transfert sans réallocation interne testé
|
||||||
|
payload hostile dans erreur absent attendu
|
||||||
|
account material hostile dans erreur absent attendu
|
||||||
|
narrowing casts absents par source canary
|
||||||
|
codec encode/decode générique absent par source canary
|
||||||
|
manifest dependency ksp-core-lib uniquement
|
||||||
|
source dependency firewall renforcé sur tous les modules production
|
||||||
|
```
|
||||||
|
|
||||||
|
Le canari `tests/external_consumer.rs` est volontairement un test d'intégration Rust séparé : Cargo le compile comme un crate consommateur externe de la bibliothèque. Il n'utilise que la façade `ksp_interface_lib::*` accessible depuis le crate-root et n'exige donc ni fixture workspace artificielle ni invocation Cargo imbriquée.
|
||||||
|
|
||||||
|
`tests/release_completeness.rs` verrouille l'inventaire exact de la foundation `0.2.13` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED
|
||||||
|
MAX_PROGRAM_INSTRUCTION_ACCOUNTS
|
||||||
|
ProgramAccountMeta
|
||||||
|
MAX_PROGRAM_INSTRUCTION_DATA_LEN
|
||||||
|
ProgramInstruction
|
||||||
|
Pubkey
|
||||||
|
```
|
||||||
|
|
||||||
|
Il verrouille également l'absence de `pub mod` et l'inventaire exact des quatre fichiers Rust de production. Ajouter une seconde famille wire dans `0.2.13` ferait ainsi échouer explicitement le canari de complétude au lieu d'élargir silencieusement la release.
|
||||||
|
|
||||||
|
Le hardening unitaire vérifie enfin que les allocations des `Vec` admis sont transférées telles quelles dans `ProgramInstruction` et que les erreurs de dépassement n'échoient ni marqueur hostile de payload ni représentation arbitraire d'account meta. Ces tests renforcent le contrat de construction sans changer l'API publique.
|
||||||
|
|
||||||
|
Après validation opérateur de `pre.005`, `pre.006` doit rester un gate technique pur sans nouveau développement fonctionnel.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/validation/016-V0_2_13_INTERFACE.md -->
|
<!-- file: docs/validation/016-V0_2_13_INTERFACE.md -->
|
||||||
<!-- version: 4 -->
|
<!-- version: 5 -->
|
||||||
|
|
||||||
# Validation `0.2.13` — Interface / wire foundation
|
# Validation `0.2.13` — Interface / wire foundation
|
||||||
|
|
||||||
@@ -140,15 +140,15 @@ Si une nouvelle dépendance externe apparaît après `pre.001`, la présente mat
|
|||||||
|
|
||||||
## 10. Tests de release
|
## 10. Tests de release
|
||||||
|
|
||||||
| Famille | Attendu | Statut |
|
| Famille | Attendu | Statut |
|
||||||
|--------------------------|-------------------------------------------------|--------------|
|
|--------------------------|-------------------------------------------------|-----------------|
|
||||||
| unit tests privés | account meta/borne puis instruction/adversarial | PASS pre.004 |
|
| unit tests privés | account meta/borne puis instruction/adversarial | PASS pre.004 |
|
||||||
| `tests/public_api.rs` | consommation crate-root uniquement | PASS pre.004 |
|
| `tests/public_api.rs` | consommation crate-root uniquement | PASS pre.004 |
|
||||||
| external consumer canary | surface utilisable hors modules privés | TODO |
|
| external consumer canary | surface utilisable hors modules privés | PRÉSENT pre.005 |
|
||||||
| dependency boundary | firewall exact | PASS pre.004 |
|
| dependency boundary | firewall exact | PASS pre.004 |
|
||||||
| release completeness | inventaire exact, aucun domaine supplémentaire | TODO |
|
| release completeness | inventaire exact, aucun domaine supplémentaire | PRÉSENT pre.005 |
|
||||||
| round-trip codec | aucun tant qu'aucun codec | N/A |
|
| round-trip codec | aucun tant qu'aucun codec | N/A |
|
||||||
| network smoke | aucun pour crate wire pure | N/A |
|
| network smoke | aucun pour crate wire pure | N/A |
|
||||||
|
|
||||||
## 11. Gate technique final attendu
|
## 11. Gate technique final attendu
|
||||||
|
|
||||||
@@ -259,3 +259,30 @@ Le statut `PASS/PENDING` de la borne accounts signifie que la valeur publique es
|
|||||||
| `cargo test --workspace` sur preuve entrée `pre.003` | NON FOURNI — à rejouer au gate opérateur 004 |
|
| `cargo test --workspace` sur preuve entrée `pre.003` | NON FOURNI — à rejouer au gate opérateur 004 |
|
||||||
|
|
||||||
La tranche ferme ainsi le premier lot générique `{ program_id, accounts, data }` sans ajouter de codec ni de comportement Program. Le hardening `pre.005` doit porter sur l'usage externe, les cas adversariaux et le firewall, pas sur une nouvelle famille wire.
|
La tranche ferme ainsi le premier lot générique `{ program_id, accounts, data }` sans ajouter de codec ni de comportement Program. Le hardening `pre.005` doit porter sur l'usage externe, les cas adversariaux et le firewall, pas sur une nouvelle famille wire.
|
||||||
|
|
||||||
|
## 16. État préparé `pre.005`
|
||||||
|
|
||||||
|
Le gate opérateur `pre.004` est complet : fmt, audits Rust/Markdown, `cargo check --workspace`, Clippy, `cargo test -p ksp-interface-lib`, `cargo test --workspace` et graphes Cargo sont verts/inspectés.
|
||||||
|
|
||||||
|
La tranche `pre.005` ajoute uniquement des canaris de hardening :
|
||||||
|
|
||||||
|
```text
|
||||||
|
external consumer crate présent via tests/external_consumer.rs
|
||||||
|
release completeness présent via tests/release_completeness.rs
|
||||||
|
crate-root exports exacts vérifiés par source canary
|
||||||
|
production module inventory exact vérifié par filesystem canary
|
||||||
|
source dependency firewall étendu à tous les src production
|
||||||
|
narrowing cast / encode/decode caché interdits par source canary
|
||||||
|
Vec accounts/data sans réallocation vérifié par identité de pointeur
|
||||||
|
payload hostile dans Error/Debug absent attendu
|
||||||
|
account material arbitraire dans Error absent attendu
|
||||||
|
nouveau type wire aucun
|
||||||
|
nouvelle dépendance aucune
|
||||||
|
codec/logging/runtime toujours absents
|
||||||
|
```
|
||||||
|
|
||||||
|
Les nouveaux tests sont **présents mais non déclarés PASS dans le sandbox**, qui ne dispose pas de Cargo. Leur statut final dépend du gate opérateur `pre.005`.
|
||||||
|
|
||||||
|
Le consumer externe est un test d'intégration Rust, donc compilé par Cargo dans un crate distinct de `ksp-interface-lib`. Sa source ne consomme que les exports du crate-root ; les modules privés ne sont pas utilisés.
|
||||||
|
|
||||||
|
Le canari de complétude impose exactement les exports publics retenus et exactement quatre modules/fichiers de production : `error.rs`, `lib.rs`, `program_account_meta.rs`, `program_instruction.rs`. Cette contrainte ferme explicitement le risque d'ajout opportuniste d'une seconde famille wire avant la release stable.
|
||||||
|
|||||||
Reference in New Issue
Block a user