From 0ee28eeb951aba23ba2bf0c863c56c9cf9611902 Mon Sep 17 00:00:00 2001 From: SinuS Von SifriduS Date: Fri, 28 Aug 2026 05:11:44 +0200 Subject: [PATCH] v0.2.13-pre.004 --- Cargo.toml | 4 +- crates/ksp-interface-lib/src/lib.rs | 7 +- .../src/program_instruction.rs | 81 +++++ .../tests/dependency_boundary.rs | 11 +- crates/ksp-interface-lib/tests/public_api.rs | 19 +- .../unit_tests/program_instruction.rs | 107 +++++++ deltas/0.2.13/pre.004.md | 295 ++++++++++++++++++ docs/plans/020-V0_2_13_INTERFACE_PLAN.md | 40 ++- docs/validation/016-V0_2_13_INTERFACE.md | 33 +- 9 files changed, 582 insertions(+), 15 deletions(-) create mode 100644 crates/ksp-interface-lib/src/program_instruction.rs create mode 100644 crates/ksp-interface-lib/unit_tests/program_instruction.rs create mode 100644 deltas/0.2.13/pre.004.md diff --git a/Cargo.toml b/Cargo.toml index 1fd1c4a..e282b94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ # file: Cargo.toml -# version: 305 +# version: 306 [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.3" +version = "0.2.13-pre.4" edition = "2024" license = "MIT" repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project" diff --git a/crates/ksp-interface-lib/src/lib.rs b/crates/ksp-interface-lib/src/lib.rs index 467b67c..f43abfc 100644 --- a/crates/ksp-interface-lib/src/lib.rs +++ b/crates/ksp-interface-lib/src/lib.rs @@ -1,5 +1,5 @@ // file: crates/ksp-interface-lib/src/lib.rs -// version: 2 +// version: 3 #![warn(missing_docs)] #![deny(unreachable_pub)] @@ -14,6 +14,7 @@ mod error; mod program_account_meta; +mod program_instruction; /// Error code used when an Interface-owned Program instruction admission limit is exceeded. pub use self::error::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED; @@ -21,5 +22,9 @@ pub use self::error::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED; pub use self::program_account_meta::MAX_PROGRAM_INSTRUCTION_ACCOUNTS; /// Passive account metadata attached to one Program instruction. pub use self::program_account_meta::ProgramAccountMeta; +/// Maximum opaque data payload admitted by one passive Program instruction contract. +pub use self::program_instruction::MAX_PROGRAM_INSTRUCTION_DATA_LEN; +/// Passive, bounded Program instruction wire contract. +pub use self::program_instruction::ProgramInstruction; /// Canonical Solana account address primitive owned by `ksp-core-lib`. pub use ksp_core_lib::Pubkey; diff --git a/crates/ksp-interface-lib/src/program_instruction.rs b/crates/ksp-interface-lib/src/program_instruction.rs new file mode 100644 index 0000000..b7cfff3 --- /dev/null +++ b/crates/ksp-interface-lib/src/program_instruction.rs @@ -0,0 +1,81 @@ +// file: crates/ksp-interface-lib/src/program_instruction.rs +// version: 1 + +/// Maximum opaque data payload admitted by one passive Program instruction contract. +pub const MAX_PROGRAM_INSTRUCTION_DATA_LEN: usize = 10 * 1024; + +/// Passive, bounded Program instruction wire contract. +/// +/// The instruction preserves the caller-provided Program identity, ordered +/// account metas and opaque data bytes without applying Program-specific +/// semantics. Construction enforces only the Interface-owned admission bounds. +pub struct ProgramInstruction { + program_id: crate::Pubkey, + accounts: std::vec::Vec, + data: std::vec::Vec, +} + +impl ProgramInstruction { + /// Creates one passive Program instruction after enforcing Interface admission bounds. + /// + /// The provided vectors are consumed directly. Their account order and duplicates + /// are preserved exactly when construction succeeds. + pub fn try_new(program_id: crate::Pubkey, accounts: std::vec::Vec, data: std::vec::Vec) -> ksp_core_lib::Result { + if accounts.len() > crate::MAX_PROGRAM_INSTRUCTION_ACCOUNTS { + return std::result::Result::Err( + ksp_core_lib::Error::new( + crate::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED, + "Program instruction account count exceeds the Interface admission limit", + ) + .with_context("field", "accounts") + .with_context("actual_len", accounts.len().to_string()) + .with_context("maximum_len", crate::MAX_PROGRAM_INSTRUCTION_ACCOUNTS.to_string()), + ); + } + if data.len() > crate::MAX_PROGRAM_INSTRUCTION_DATA_LEN { + return std::result::Result::Err( + ksp_core_lib::Error::new( + crate::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED, + "Program instruction data length exceeds the Interface admission limit", + ) + .with_context("field", "data") + .with_context("actual_len", data.len().to_string()) + .with_context("maximum_len", crate::MAX_PROGRAM_INSTRUCTION_DATA_LEN.to_string()), + ); + } + return std::result::Result::Ok(Self { program_id, accounts, data }); + } + + /// Returns the ordered account metas exactly as admitted at construction. + #[must_use] + pub fn accounts(&self) -> &[crate::ProgramAccountMeta] { + return self.accounts.as_slice(); + } + + /// Returns the opaque Program instruction data bytes. + #[must_use] + pub fn data(&self) -> &[u8] { + return self.data.as_slice(); + } + + /// Returns the Program identity. + #[must_use] + pub const fn program_id(&self) -> &crate::Pubkey { + return &self.program_id; + } +} + +impl std::fmt::Debug for ProgramInstruction { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + return formatter + .debug_struct("ProgramInstruction") + .field("program_id", &self.program_id) + .field("account_count", &self.accounts.len()) + .field("data_len", &self.data.len()) + .finish(); + } +} + +#[cfg(test)] +#[path = "../unit_tests/program_instruction.rs"] +mod tests; diff --git a/crates/ksp-interface-lib/tests/dependency_boundary.rs b/crates/ksp-interface-lib/tests/dependency_boundary.rs index 031c3aa..a0b63df 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: 2 +// version: 3 //! Dependency and passive-surface canaries for the Interface foundation. @@ -45,15 +45,20 @@ fn pre_002_manifest_has_exact_core_only_runtime_dependency() { } #[test] -fn pre_003_surface_remains_passive_without_instruction_or_runtime_logging() { +fn pre_004_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")); assert!(crate_root.contains("ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED")); - assert!(!crate_root.contains("ProgramInstruction")); + assert!(crate_root.contains("ProgramInstruction")); + assert!(crate_root.contains("MAX_PROGRAM_INSTRUCTION_DATA_LEN")); assert!(!crate_root.contains("TRACING_TARGET")); assert!(!crate_root.contains("ksp_logging_lib")); assert!(!std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src/constants.rs").exists()); + let instruction_source = include_str!("../src/program_instruction.rs"); + for forbidden in ["serde", "borsh", "bincode", "wincode", "solana_instruction", "ksp_logging_lib", "TRACING_TARGET"] { + assert!(!instruction_source.contains(forbidden), "forbidden Interface surface detected: {forbidden}"); + } return; } diff --git a/crates/ksp-interface-lib/tests/public_api.rs b/crates/ksp-interface-lib/tests/public_api.rs index 2633799..271a611 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: 2 +// version: 3 //! Integration canaries for the public `ksp-interface-lib` foundation. @@ -36,3 +36,20 @@ fn public_pre_003_interface_error_code_is_stable_and_core_owned() { assert_eq!(code.code(), "program_instruction_limit_exceeded"); return; } + +#[test] +fn public_pre_004_program_instruction_contract_is_available_from_crate_root() { + let program_id = ksp_interface_lib::Pubkey::new_from_array([4_u8; 32]); + let account = ksp_interface_lib::ProgramAccountMeta::readonly(ksp_interface_lib::Pubkey::new_from_array([5_u8; 32]), true); + let instruction = ksp_interface_lib::ProgramInstruction::try_new(program_id, std::vec![account], std::vec![1_u8, 2, 3]); + assert!(instruction.is_ok()); + let instruction = match instruction { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + assert_eq!(instruction.program_id(), &program_id); + assert_eq!(instruction.accounts(), &[account]); + assert_eq!(instruction.data(), &[1_u8, 2, 3]); + assert_eq!(ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_DATA_LEN, 10_240); + return; +} diff --git a/crates/ksp-interface-lib/unit_tests/program_instruction.rs b/crates/ksp-interface-lib/unit_tests/program_instruction.rs new file mode 100644 index 0000000..7eb0d19 --- /dev/null +++ b/crates/ksp-interface-lib/unit_tests/program_instruction.rs @@ -0,0 +1,107 @@ +// file: crates/ksp-interface-lib/unit_tests/program_instruction.rs +// version: 1 + +fn meta(byte: u8, writable: bool) -> crate::ProgramAccountMeta { + let pubkey = crate::Pubkey::new_from_array([byte; 32]); + if writable { + return crate::ProgramAccountMeta::writable(pubkey, false); + } + return crate::ProgramAccountMeta::readonly(pubkey, false); +} + +#[test] +fn instruction_accepts_empty_accounts_and_data_with_opaque_program_id() { + let program_id = crate::Pubkey::new_from_array([0xD3_u8; 32]); + let instruction = crate::ProgramInstruction::try_new(program_id, std::vec::Vec::new(), std::vec::Vec::new()); + assert!(instruction.is_ok()); + let instruction = match instruction { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + assert_eq!(instruction.program_id(), &program_id); + assert!(instruction.accounts().is_empty()); + assert!(instruction.data().is_empty()); + return; +} + +#[test] +fn instruction_preserves_account_order_duplicates_and_opaque_data() { + let program_id = crate::Pubkey::new_from_array([0x41_u8; 32]); + let first = meta(1, false); + let duplicate = meta(2, true); + let accounts = std::vec![first, duplicate, first]; + let data = std::vec![0_u8, 1, 2, 0xFF]; + let instruction = crate::ProgramInstruction::try_new(program_id, accounts, data.clone()); + assert!(instruction.is_ok()); + let instruction = match instruction { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + assert_eq!(instruction.accounts(), &[first, duplicate, first]); + assert_eq!(instruction.data(), data.as_slice()); + return; +} + +#[test] +fn instruction_accepts_account_limit_and_rejects_one_above_it() { + let program_id = crate::Pubkey::new_from_array([0x51_u8; 32]); + let account = meta(7, false); + let accepted = crate::ProgramInstruction::try_new(program_id, std::vec![account; crate::MAX_PROGRAM_INSTRUCTION_ACCOUNTS], std::vec::Vec::new()); + assert!(accepted.is_ok()); + let rejected = crate::ProgramInstruction::try_new(program_id, std::vec![account; crate::MAX_PROGRAM_INSTRUCTION_ACCOUNTS + 1], std::vec::Vec::new()); + assert!(rejected.is_err()); + let error = match rejected { + std::result::Result::Err(value) => value, + std::result::Result::Ok(_) => return, + }; + assert_eq!(error.code(), crate::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED); + assert_eq!(error.message(), "Program instruction account count exceeds the Interface admission limit"); + assert_eq!(error.context().len(), 3); + assert_eq!(error.context()[0].key(), "field"); + assert_eq!(error.context()[0].value(), "accounts"); + assert_eq!(error.context()[1].key(), "actual_len"); + assert_eq!(error.context()[1].value(), "256"); + assert_eq!(error.context()[2].key(), "maximum_len"); + assert_eq!(error.context()[2].value(), "255"); + return; +} + +#[test] +fn instruction_accepts_data_limit_and_rejects_one_byte_above_it() { + let program_id = crate::Pubkey::new_from_array([0x61_u8; 32]); + let accepted = crate::ProgramInstruction::try_new(program_id, std::vec::Vec::new(), std::vec![0xA5_u8; crate::MAX_PROGRAM_INSTRUCTION_DATA_LEN]); + assert!(accepted.is_ok()); + let rejected = crate::ProgramInstruction::try_new(program_id, std::vec::Vec::new(), std::vec![0x5A_u8; crate::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(), crate::ERROR_CODE_PROGRAM_INSTRUCTION_LIMIT_EXCEEDED); + assert_eq!(error.message(), "Program instruction data length exceeds the Interface admission limit"); + assert_eq!(error.context().len(), 3); + assert_eq!(error.context()[0].value(), "data"); + assert_eq!(error.context()[1].value(), "10241"); + assert_eq!(error.context()[2].value(), "10240"); + return; +} + +#[test] +fn instruction_debug_is_bounded_and_omits_accounts_and_payload_bytes() { + let program_id = crate::Pubkey::new_from_array([0x71_u8; 32]); + let payload = b"PAYLOAD_SENTINEL_NEVER_RENDER".to_vec(); + let instruction = crate::ProgramInstruction::try_new(program_id, std::vec![meta(8, false), meta(9, true)], payload); + assert!(instruction.is_ok()); + let instruction = match instruction { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + let rendered = std::format!("{instruction:?}"); + assert!(rendered.contains("ProgramInstruction")); + assert!(rendered.contains("account_count: 2")); + assert!(rendered.contains("data_len: 29")); + assert!(!rendered.contains("PAYLOAD_SENTINEL_NEVER_RENDER")); + assert!(!rendered.contains("is_signer")); + assert!(!rendered.contains("is_writable")); + return; +} diff --git a/deltas/0.2.13/pre.004.md b/deltas/0.2.13/pre.004.md new file mode 100644 index 0000000..ce325a1 --- /dev/null +++ b/deltas/0.2.13/pre.004.md @@ -0,0 +1,295 @@ + + + +# Delta `0.2.13-pre.004` — `ProgramInstruction` passif borné + +## 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 +``` + +La preuve opérateur fournie pour `pre.003` confirme : + +```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 tree -p ksp-interface-lib --edges normal inspecté +cargo tree --duplicates inspecté +``` + +`cargo test --workspace` n'apparaît pas dans cette preuve opérateur. La présente préparation n'en invente donc pas le résultat ; ce test devra être rejoué avec le gate de `pre.004` avant de déclarer la continuité workspace complète. + +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.3 +``` + +à : + +```text +0.2.13-pre.4 +``` + +## 2. Objectif + +Matérialiser le second type du premier lot wire générique décidé en `pre.001` : une instruction Program passive `{ program_id, accounts, data }`, bornée à l'admission et sans comportement Program. + +La tranche ajoute : + +```text +ProgramInstruction +MAX_PROGRAM_INSTRUCTION_DATA_LEN = 10_240 +ProgramInstruction::try_new(...) +accessors program_id/accounts/data +Debug résumé +unit/public canaries de limites, ordre et doublons +``` + +Elle n'ajoute pas : + +```text +serde / serde_json +borsh / bincode / wincode +solana-instruction +codec ou discriminant Program spécifique +Program API / Program behavior +Transport / Store / Config / Wallet +ksp-logging-lib / constants.rs / TRACING_TARGET +runtime réseau +``` + +## 3. `ProgramInstruction` + +Le type possède trois champs privés : + +```text +program_id : Pubkey +accounts : Vec +data : Vec +``` + +Le constructeur public est : + +```text +ProgramInstruction::try_new(program_id, accounts, data) + -> ksp_core_lib::Result +``` + +Les accessors publics sont : + +```text +program_id() -> &Pubkey +accounts() -> &[ProgramAccountMeta] +data() -> &[u8] +``` + +La construction consomme directement les `Vec` fournis. Aucun clone interne, conversion vers `solana-instruction` ou sérialisation n'est ajouté. + +## 4. Bornes d'admission + +Les deux plafonds sont désormais matérialisés : + +```text +MAX_PROGRAM_INSTRUCTION_ACCOUNTS = 255 +MAX_PROGRAM_INSTRUCTION_DATA_LEN = 10_240 +``` + +`try_new` vérifie en `usize` : + +```text +accounts.len() <= 255 +data.len() <= 10_240 +``` + +Canaris exacts : + +```text +255 accounts accepté +256 accounts refusé +10_240 octets data accepté +10_241 octets data refusé +``` + +Ces valeurs restent des bornes d'admission Interface fondées sur les plafonds Solana CPI audités en `pre.001`. Elles ne garantissent pas qu'une instruction rentre dans une transaction top-level complète. + +## 5. Sémantique passive + +Restent valides : + +```text +accounts vides +data vide +Program Pubkey inconnu du registry Core +doublons d'accounts +ordre arbitraire des account metas +payload opaque quelconque sous la borne +``` + +Interface n'ajoute aucune politique signer/writable, aucun lookup de Program ID, aucune déduplication et aucune interprétation du payload. + +## 6. Erreurs + +Les deux violations de borne réutilisent : + +```text +interface.program_instruction_limit_exceeded +``` + +via `ksp_core_lib::Error/Result`. + +Le contexte d'erreur est limité à : + +```text +field +actual_len +maximum_len +``` + +Aucun octet `data`, aucun account arbitraire et aucun payload externe n'est copié dans l'erreur. + +Le code conceptuel `invalid_program_instruction` reste absent : la foundation ne possède toujours aucun invariant de forme distinct des deux limites. + +## 7. Debug borné + +`ProgramInstruction` n'utilise pas un `derive(Debug)` qui imprimerait les deux `Vec`. + +Son `Debug` manuel expose uniquement : + +```text +program_id +account_count +data_len +``` + +Les account metas et le payload sont volontairement absents du rendu. + +## 8. Logging + +La décision de `pre.002` reste inchangée : cette crate est passive. + +Restent absents : + +```text +ksp-logging-lib +src/constants.rs +TRACING_TARGET +tracing direct +``` + +Une erreur de validation de borne est une valeur retournée au caller, pas un événement runtime à logger depuis Interface. + +## 9. Tests + +Les unit tests de `ProgramInstruction` couvrent : + +```text +accounts/data vides acceptés +Program Pubkey opaque accepté +ordre et doublons préservés +payload opaque préservé +255 / 256 accounts +10_240 / 10_241 octets data +code/message/contexte d'erreur sûr +Debug résumé sans payload ni account flags +``` + +`tests/public_api.rs` ajoute un canari de consommation crate-root de `ProgramInstruction` et de `MAX_PROGRAM_INSTRUCTION_DATA_LEN`. + +`tests/dependency_boundary.rs` est avancé à `pre.004` et confirme toujours : + +```text +ksp-core-lib seule dépendance normale +aucun serde/borsh/bincode/wincode +aucun solana-instruction +aucun logging/runtime +ProgramInstruction présent via façade explicite +``` + +## 10. Fichiers ajoutés + +```text +crates/ksp-interface-lib/src/program_instruction.rs +crates/ksp-interface-lib/unit_tests/program_instruction.rs +deltas/0.2.13/pre.004.md +``` + +## 11. Fichiers modifiés + +```text +Cargo.toml +crates/ksp-interface-lib/src/lib.rs +crates/ksp-interface-lib/tests/dependency_boundary.rs +crates/ksp-interface-lib/tests/public_api.rs +docs/plans/020-V0_2_13_INTERFACE_PLAN.md +docs/validation/016-V0_2_13_INTERFACE.md +``` + +## 12. 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/error.rs +crates/ksp-interface-lib/src/program_account_meta.rs +crates/ksp-interface-lib/unit_tests/program_account_meta.rs +docs/architecture/** +prompts/** +config/** +``` + +## 13. 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 +``` + +Le `cargo test --workspace` est particulièrement requis ici puisqu'il n'était pas présent dans la preuve opérateur fournie pour `pre.003`. + +## 14. Suite autorisée + +Après gate vert, `pre.005` doit rester une tranche de hardening : + +```text +cas adversariaux complémentaires +consumer externe +surface crate-root +firewall source/manifest +graphes Cargo +canari de complétude du premier lot +``` + +Aucun second domaine wire, codec générique ou comportement Program ne doit être ajouté opportunément. diff --git a/docs/plans/020-V0_2_13_INTERFACE_PLAN.md b/docs/plans/020-V0_2_13_INTERFACE_PLAN.md index cb0be42..ece5bd5 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 @@ -389,13 +389,13 @@ Créer la crate, l'ajouter au workspace, poser `lib.rs` avec exports explicites, ### pre.003 — `ProgramAccountMeta` + bornes communes -**Statut : réalisé ; gate opérateur à confirmer.** +**Statut : réalisé ; fmt/audits/check/Clippy/tests Interface PASS, `cargo test --workspace` non fourni dans la preuve opérateur.** Matérialiser la primitive de compte ordonné, la borne accounts, le modèle d'erreur Interface minimal et les unit/public canaries associés. `ProgramInstruction` et la borne data restent réservés à `pre.004`. ### pre.004 — `ProgramInstruction` passif borné -**Statut : prévu** +**Statut : réalisé ; gate opérateur à confirmer.** 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. @@ -502,4 +502,36 @@ La constante `255` ferme le contrat public de borne accounts mais son enforcemen Une seule catégorie d'erreur Interface est introduite. `invalid_program_instruction` reste absent : aucun invariant de forme distinct des limites n'est encore matérialisé et le créer par symétrie serait prématuré. Les futures erreurs de borne doivent utiliser `ksp_core_lib::Error/Result` et ne projeter que des longueurs/plafonds sûrs. -Le passage à `pre.004` reste conditionné au gate opérateur complet sur l'overlay `pre.003`. +La preuve opérateur fournie pour `pre.003` confirme fmt, audits, `cargo check`, Clippy, tests ciblés Interface et graphes Cargo. `cargo test --workspace` n'apparaît pas dans cette preuve ; il doit donc être rejoué au gate de `pre.004` avant de déclarer la continuité workspace complète. + +## 18. État préparé `pre.004` + +`pre.004` matérialise le second et dernier type du premier lot wire générique retenu : + +```text +ProgramInstruction présent +fields privés +program_id Pubkey Core +accounts Vec consommé +data Vec opaque consommé +MAX_PROGRAM_INSTRUCTION_ACCOUNTS 255 +MAX_PROGRAM_INSTRUCTION_DATA_LEN 10_240 +try_new bornes accounts puis data +accounts 255 / 256 accepté / refusé +data 10_240 / 10_241 accepté / refusé +accounts/data vides acceptés +ordre/doublons préservés +Program Pubkey non registry accepté +Debug program_id/account_count/data_len uniquement +serde / borsh / bincode / wincode absents +solana-instruction absent +ksp-logging-lib / constants.rs / TRACING_TARGET absents +``` + +`ProgramInstruction::try_new` consomme les deux `Vec` fournis et compare leurs longueurs en `usize` avant stockage dans l'objet. La foundation n'ajoute aucun clone, cast étroit ou allocation de conversion. Une allocation éventuellement déjà réalisée par l'appelant pour construire le `Vec` reste naturellement hors du contrôle de l'API. + +Les deux limites utilisent le code commun `interface.program_instruction_limit_exceeded`. Le contexte d'erreur est volontairement réduit à `field`, `actual_len` et `maximum_len`; aucune donnée du payload ni aucun account arbitraire n'est recopié dans le 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. diff --git a/docs/validation/016-V0_2_13_INTERFACE.md b/docs/validation/016-V0_2_13_INTERFACE.md index 2773d3f..6531029 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 @@ -142,10 +142,10 @@ Si une nouvelle dépendance externe apparaît après `pre.001`, la présente mat | Famille | Attendu | Statut | |--------------------------|-------------------------------------------------|--------------| -| unit tests privés | account meta/borne puis instruction/adversarial | PARTIEL | -| `tests/public_api.rs` | consommation crate-root uniquement | PASS pre.003 | +| 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.002 | +| 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 | @@ -234,3 +234,28 @@ Le passage à `pre.003` reste conditionné au gate opérateur complet sur l'over | dépendance normale | `ksp-core-lib` uniquement | Le statut `PASS/PENDING` de la borne accounts signifie que la valeur publique est désormais figée à `255`, tandis que le rejet concret de `256` comptes sera testé lorsque la collection existera dans `ProgramInstruction`. Aucun faux wrapper de collection n'est créé dans `pre.003`. + +## 15. État préparé `pre.004` + +| Critère | Statut | +|------------------------------------------------------|----------------------------------------------| +| `ProgramInstruction` crate-root | PASS structurel | +| champs privés | PASS | +| constructeur `try_new` | PASS | +| accessors `program_id/accounts/data` | PASS | +| `MAX_PROGRAM_INSTRUCTION_DATA_LEN == 10_240` | PASS unit + public API | +| accounts `255` accepté / `256` refusé | PASS unit | +| data `10_240` accepté / `10_241` refusé | PASS unit | +| accounts/data vides | PASS unit | +| ordre + doublons accounts | PASS unit | +| Program Pubkey opaque/non registry | PASS unit | +| erreur borne Core commune | PASS | +| contexte erreur limité aux longueurs/plafonds sûrs | PASS unit | +| Debug résumé sans accounts/payload | PASS unit | +| serde/codecs | ABSENTS | +| `solana-instruction` | ABSENT | +| logging/runtime | ABSENTS | +| dépendance normale | `ksp-core-lib` uniquement | +| `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.