From 29f27ae1099fe2a80b18e2df8039d25ee32ae61e Mon Sep 17 00:00:00 2001 From: SinuS Von SifriduS Date: Fri, 28 Aug 2026 05:24:26 +0200 Subject: [PATCH] v0.2.13-pre.005 --- Cargo.toml | 4 +- .../tests/dependency_boundary.rs | 50 +++- .../tests/external_consumer.rs | 27 +++ crates/ksp-interface-lib/tests/public_api.rs | 26 ++- .../tests/release_completeness.rs | 75 ++++++ .../unit_tests/program_instruction.rs | 60 ++++- deltas/0.2.13/pre.005.md | 213 ++++++++++++++++++ docs/plans/020-V0_2_13_INTERFACE_PLAN.md | 47 +++- docs/validation/016-V0_2_13_INTERFACE.md | 47 +++- 9 files changed, 529 insertions(+), 20 deletions(-) create mode 100644 crates/ksp-interface-lib/tests/external_consumer.rs create mode 100644 crates/ksp-interface-lib/tests/release_completeness.rs create mode 100644 deltas/0.2.13/pre.005.md diff --git a/Cargo.toml b/Cargo.toml index e282b94..f894acf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ # file: Cargo.toml -# version: 306 +# version: 307 [workspace] 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"] [workspace.package] -version = "0.2.13-pre.4" +version = "0.2.13-pre.5" edition = "2024" license = "MIT" repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project" diff --git a/crates/ksp-interface-lib/tests/dependency_boundary.rs b/crates/ksp-interface-lib/tests/dependency_boundary.rs index a0b63df..a50380f 100644 --- a/crates/ksp-interface-lib/tests/dependency_boundary.rs +++ b/crates/ksp-interface-lib/tests/dependency_boundary.rs @@ -1,5 +1,5 @@ // file: crates/ksp-interface-lib/tests/dependency_boundary.rs -// version: 3 +// version: 4 //! Dependency and passive-surface canaries for the Interface foundation. @@ -45,7 +45,7 @@ fn pre_002_manifest_has_exact_core_only_runtime_dependency() { } #[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"); assert!(crate_root.contains("ProgramAccountMeta")); 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(); 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; +} diff --git a/crates/ksp-interface-lib/tests/external_consumer.rs b/crates/ksp-interface-lib/tests/external_consumer.rs new file mode 100644 index 0000000..eed317c --- /dev/null +++ b/crates/ksp-interface-lib/tests/external_consumer.rs @@ -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; +} diff --git a/crates/ksp-interface-lib/tests/public_api.rs b/crates/ksp-interface-lib/tests/public_api.rs index 271a611..7d2bea9 100644 --- a/crates/ksp-interface-lib/tests/public_api.rs +++ b/crates/ksp-interface-lib/tests/public_api.rs @@ -1,5 +1,5 @@ // file: crates/ksp-interface-lib/tests/public_api.rs -// version: 3 +// version: 4 //! 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); 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; +} diff --git a/crates/ksp-interface-lib/tests/release_completeness.rs b/crates/ksp-interface-lib/tests/release_completeness.rs new file mode 100644 index 0000000..8b9e6ec --- /dev/null +++ b/crates/ksp-interface-lib/tests/release_completeness.rs @@ -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; +} diff --git a/crates/ksp-interface-lib/unit_tests/program_instruction.rs b/crates/ksp-interface-lib/unit_tests/program_instruction.rs index 7eb0d19..f696215 100644 --- a/crates/ksp-interface-lib/unit_tests/program_instruction.rs +++ b/crates/ksp-interface-lib/unit_tests/program_instruction.rs @@ -1,5 +1,5 @@ // file: crates/ksp-interface-lib/unit_tests/program_instruction.rs -// version: 1 +// version: 2 fn meta(byte: u8, writable: bool) -> crate::ProgramAccountMeta { 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")); 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; +} diff --git a/deltas/0.2.13/pre.005.md b/deltas/0.2.13/pre.005.md new file mode 100644 index 0000000..88d2b5c --- /dev/null +++ b/deltas/0.2.13/pre.005.md @@ -0,0 +1,213 @@ + + + +# 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` et `Vec` 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. diff --git a/docs/plans/020-V0_2_13_INTERFACE_PLAN.md b/docs/plans/020-V0_2_13_INTERFACE_PLAN.md index ece5bd5..979e76a 100644 --- a/docs/plans/020-V0_2_13_INTERFACE_PLAN.md +++ b/docs/plans/020-V0_2_13_INTERFACE_PLAN.md @@ -1,5 +1,5 @@ - + # 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é -**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. ### 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. @@ -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 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. diff --git a/docs/validation/016-V0_2_13_INTERFACE.md b/docs/validation/016-V0_2_13_INTERFACE.md index 6531029..94f9223 100644 --- a/docs/validation/016-V0_2_13_INTERFACE.md +++ b/docs/validation/016-V0_2_13_INTERFACE.md @@ -1,5 +1,5 @@ - + # 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 -| Famille | Attendu | Statut | -|--------------------------|-------------------------------------------------|--------------| -| unit tests privés | account meta/borne puis instruction/adversarial | PASS pre.004 | -| `tests/public_api.rs` | consommation crate-root uniquement | PASS pre.004 | -| external consumer canary | surface utilisable hors modules privés | TODO | -| dependency boundary | firewall exact | PASS pre.004 | -| release completeness | inventaire exact, aucun domaine supplémentaire | TODO | -| round-trip codec | aucun tant qu'aucun codec | N/A | -| network smoke | aucun pour crate wire pure | N/A | +| Famille | Attendu | Statut | +|--------------------------|-------------------------------------------------|-----------------| +| unit tests privés | account meta/borne puis instruction/adversarial | PASS pre.004 | +| `tests/public_api.rs` | consommation crate-root uniquement | PASS pre.004 | +| external consumer canary | surface utilisable hors modules privés | PRÉSENT pre.005 | +| dependency boundary | firewall exact | PASS pre.004 | +| release completeness | inventaire exact, aucun domaine supplémentaire | PRÉSENT pre.005 | +| round-trip codec | aucun tant qu'aucun codec | N/A | +| network smoke | aucun pour crate wire pure | N/A | ## 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 | 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.