From 0319794e59de979a7c79656b76d7a66d4a9e0f26 Mon Sep 17 00:00:00 2001 From: SinuS Von SifriduS Date: Fri, 28 Aug 2026 15:12:59 +0200 Subject: [PATCH] v0.2.14-pre.005 --- Cargo.toml | 4 +- .../tests/release_completeness.rs | 138 +++++++++++ .../tests/security_hardening.rs | 156 +++++++++++++ deltas/0.2.14/pre.005.md | 217 ++++++++++++++++++ docs/plans/021-V0_2_14_PROGRAM_API_PLAN.md | 32 ++- docs/validation/017-V0_2_14_PROGRAM_API.md | 52 ++++- 6 files changed, 589 insertions(+), 10 deletions(-) create mode 100644 crates/ksp-program-api/tests/release_completeness.rs create mode 100644 crates/ksp-program-api/tests/security_hardening.rs create mode 100644 deltas/0.2.14/pre.005.md diff --git a/Cargo.toml b/Cargo.toml index 2446ac0..b828454 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,12 @@ # file: Cargo.toml -# version: 316 +# version: 317 [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-program-api", "crates/ksp-wallet-lib"] [workspace.package] -version = "0.2.14-pre.4" +version = "0.2.14-pre.5" edition = "2024" license = "MIT" repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project" diff --git a/crates/ksp-program-api/tests/release_completeness.rs b/crates/ksp-program-api/tests/release_completeness.rs new file mode 100644 index 0000000..6814f4a --- /dev/null +++ b/crates/ksp-program-api/tests/release_completeness.rs @@ -0,0 +1,138 @@ +// file: crates/ksp-program-api/tests/release_completeness.rs +// version: 1 + +//! Release-level completeness canaries for the `0.2.14` Program API 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::program_instruction_decode::ProgramInstructionDecodeOutcome;", + "pub use self::program_instruction_decode::ProgramInstructionRecognition;", + "pub use self::program_instruction_decoder::ProgramInstructionDecoder;", + "pub use ksp_core_lib::Error;", + "pub use ksp_core_lib::ErrorCode;", + "pub use ksp_core_lib::ErrorContext;", + "pub use ksp_core_lib::Pubkey;", + "pub use ksp_core_lib::Result;", + "pub use ksp_interface_lib::ProgramAccountMeta;", + "pub use ksp_interface_lib::ProgramInstruction;", + ]; + expected.sort_unstable(); + assert_eq!(actual, expected); + assert!(!crate_root.contains("pub mod ")); + return; +} + +#[test] +fn pre_005_production_module_inventory_is_instruction_only() -> 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!["lib.rs", "program_instruction_decode.rs", "program_instruction_decoder.rs"]); + return std::result::Result::Ok(()); +} + +#[test] +fn pre_005_public_enum_and_trait_inventory_remains_open_world() { + let sources = [ + include_str!("../src/lib.rs"), + include_str!("../src/program_instruction_decode.rs"), + include_str!("../src/program_instruction_decoder.rs"), + ]; + let mut public_enums = std::vec::Vec::new(); + let mut public_traits = std::vec::Vec::new(); + for source in sources { + for line in source.lines() { + let trimmed = line.trim(); + if trimmed.starts_with("pub enum ") { + public_enums.push(trimmed); + } + if trimmed.starts_with("pub trait ") { + public_traits.push(trimmed); + } + } + } + public_enums.sort_unstable(); + public_traits.sort_unstable(); + assert_eq!( + public_enums, + std::vec!["pub enum ProgramInstructionDecodeOutcome {", "pub enum ProgramInstructionRecognition {"] + ); + assert_eq!(public_traits, std::vec!["pub trait ProgramInstructionDecoder: Send + Sync {"]); + let decode_source = include_str!("../src/program_instruction_decode.rs"); + assert!(decode_source.contains("#[non_exhaustive]\npub enum ProgramInstructionRecognition")); + assert!(decode_source.contains("#[non_exhaustive]\npub enum ProgramInstructionDecodeOutcome")); + return; +} + +#[test] +fn pre_005_production_sources_have_no_registry_preparer_codec_or_runtime_creep() { + let sources = [ + include_str!("../src/lib.rs"), + include_str!("../src/program_instruction_decode.rs"), + include_str!("../src/program_instruction_decoder.rs"), + ]; + for source in sources { + for forbidden in [ + "pub enum ProgramKind", + "pub struct ProgramRegistry", + "pub trait ProgramAccountDecoder", + "pub trait ProgramEventDecoder", + "pub trait ProgramReturnDataDecoder", + "pub trait ProgramExecutionPreparer", + "std::any::Any", + "serde::", + "serde_json::", + "borsh::", + "bincode::", + "wincode::", + "ksp_logging_lib::", + "tracing::", + "reqwest::", + "tokio::", + "tonic::", + "tauri::", + "std::env::", + "std::fs::", + "std::net::", + "dyn ProgramInstructionDecoder", + "dyn crate::ProgramInstructionDecoder", + ] { + assert!(!source.contains(forbidden), "forbidden Program API production surface detected: {forbidden}"); + } + } + return; +} diff --git a/crates/ksp-program-api/tests/security_hardening.rs b/crates/ksp-program-api/tests/security_hardening.rs new file mode 100644 index 0000000..620f48d --- /dev/null +++ b/crates/ksp-program-api/tests/security_hardening.rs @@ -0,0 +1,156 @@ +// file: crates/ksp-program-api/tests/security_hardening.rs +// version: 1 + +//! Adversarial and bound-safety canaries for the Program API foundation. + +const HOSTILE_MARKER: &str = "PROGRAM-SECRET-CANARY"; +const MALFORMED_OPCODE: u8 = 0xFF_u8; +const PROGRAM_ID_BYTES: [u8; 32] = [0xD1_u8; 32]; + +struct BoundsObserved { + account_count: usize, + data_len: usize, +} + +struct BoundedDecoder { + program_ids: [ksp_program_api::Pubkey; 1], +} + +impl BoundedDecoder { + fn new() -> Self { + return Self { program_ids: [ksp_program_api::Pubkey::new_from_array(PROGRAM_ID_BYTES)] }; + } +} + +impl ksp_program_api::ProgramInstructionDecoder for BoundedDecoder { + type Decoded = BoundsObserved; + + fn program_ids(&self) -> &[ksp_program_api::Pubkey] { + return &self.program_ids; + } + + fn recognize(&self, instruction: &ksp_program_api::ProgramInstruction) -> ksp_program_api::ProgramInstructionRecognition { + if instruction.program_id() == &self.program_ids[0] { + return ksp_program_api::ProgramInstructionRecognition::ExactMatch; + } + return ksp_program_api::ProgramInstructionRecognition::NoMatch; + } + + fn decode( + &self, + instruction: &ksp_program_api::ProgramInstruction, + ) -> ksp_program_api::Result> { + if instruction.data().first() == std::option::Option::Some(&MALFORMED_OPCODE) { + return std::result::Result::Err(ksp_program_api::Error::new( + ksp_program_api::ErrorCode::new("program_test", "malformed_instruction"), + "malformed external Program instruction", + )); + } + return std::result::Result::Ok(ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(BoundsObserved { + account_count: instruction.accounts().len(), + data_len: instruction.data().len(), + })); + } +} + +struct BoundlessDecoded { + marker: std::rc::Rc>, +} + +struct BoundlessOutputDecoder; + +impl ksp_program_api::ProgramInstructionDecoder for BoundlessOutputDecoder { + type Decoded = BoundlessDecoded; + + fn program_ids(&self) -> &[ksp_program_api::Pubkey] { + return &[]; + } + + fn recognize(&self, _instruction: &ksp_program_api::ProgramInstruction) -> ksp_program_api::ProgramInstructionRecognition { + return ksp_program_api::ProgramInstructionRecognition::NoMatch; + } + + fn decode( + &self, + _instruction: &ksp_program_api::ProgramInstruction, + ) -> ksp_program_api::Result> { + return std::result::Result::Ok(ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(BoundlessDecoded { + marker: std::rc::Rc::new(std::cell::Cell::new(0x5A_u8)), + })); + } +} + +#[test] +fn pre_005_max_interface_instruction_crosses_decoder_boundary_without_new_contract() { + let decoder = BoundedDecoder::new(); + let account = ksp_program_api::ProgramAccountMeta::readonly(ksp_program_api::Pubkey::new_from_array([0xD2_u8; 32]), false); + let accounts = std::vec![account; ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_ACCOUNTS]; + let data = std::vec![0x5A_u8; ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_DATA_LEN]; + let instruction = ksp_program_api::ProgramInstruction::try_new(decoder.program_ids[0], accounts, data); + assert!(instruction.is_ok()); + let instruction = match instruction { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + let outcome = ksp_program_api::ProgramInstructionDecoder::decode(&decoder, &instruction); + assert!(outcome.is_ok()); + let outcome = match outcome { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + let observed = match outcome { + ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(value) => value, + _ => return, + }; + assert_eq!(observed.account_count, ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_ACCOUNTS); + assert_eq!(observed.data_len, ksp_interface_lib::MAX_PROGRAM_INSTRUCTION_DATA_LEN); + return; +} + +#[test] +fn pre_005_malformed_payload_error_path_does_not_gain_automatic_payload_echo() { + let decoder = BoundedDecoder::new(); + let mut payload = std::vec![MALFORMED_OPCODE]; + payload.extend_from_slice(HOSTILE_MARKER.as_bytes()); + let instruction = ksp_program_api::ProgramInstruction::try_new(decoder.program_ids[0], std::vec![], payload); + assert!(instruction.is_ok()); + let instruction = match instruction { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + let outcome = ksp_program_api::ProgramInstructionDecoder::decode(&decoder, &instruction); + assert!(outcome.is_err()); + let error = match outcome { + std::result::Result::Err(value) => value, + std::result::Result::Ok(_) => return, + }; + assert_eq!(error.code().domain(), "program_test"); + assert_eq!(error.code().code(), "malformed_instruction"); + assert!(!std::format!("{error}").contains(HOSTILE_MARKER)); + assert!(!std::format!("{error:?}").contains(HOSTILE_MARKER)); + return; +} + +#[test] +fn pre_005_associated_decoded_type_keeps_no_implicit_debug_clone_send_or_sync_bound() { + let decoder = BoundlessOutputDecoder; + let instruction = ksp_program_api::ProgramInstruction::try_new(ksp_program_api::Pubkey::new_from_array([0xD3_u8; 32]), std::vec![], std::vec![]); + assert!(instruction.is_ok()); + let instruction = match instruction { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + let outcome = ksp_program_api::ProgramInstructionDecoder::decode(&decoder, &instruction); + assert!(outcome.is_ok()); + let outcome = match outcome { + std::result::Result::Ok(value) => value, + std::result::Result::Err(_) => return, + }; + assert_eq!(std::format!("{outcome:?}"), "Decoded"); + let decoded = match outcome { + ksp_program_api::ProgramInstructionDecodeOutcome::Decoded(value) => value, + _ => return, + }; + assert_eq!(decoded.marker.get(), 0x5A_u8); + return; +} diff --git a/deltas/0.2.14/pre.005.md b/deltas/0.2.14/pre.005.md new file mode 100644 index 0000000..d3a2a1a --- /dev/null +++ b/deltas/0.2.14/pre.005.md @@ -0,0 +1,217 @@ + + + +# Delta `0.2.14-pre.005` — adversarial/API hardening + completeness + +## 1. Base requise + +Cette tranche s'applique exclusivement sur : + +```text +v0.2.13 ++ 0.2.14-pre.001 ++ 0.2.14-pre.002 ++ 0.2.14-pre.003 ++ 0.2.14-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 — 172 tables / 121 fichiers +cargo check --workspace PASS +cargo clippy --workspace --all-targets PASS +cargo test -p ksp-program-api PASS — 11 tests Rust +cargo test --workspace PASS +cargo tree -p ksp-program-api --edges normal PASS — Core + Interface uniquement +cargo tree --duplicates exécuté +``` + +La version workspace passe de : + +```text +0.2.14-pre.4 +``` + +à : + +```text +0.2.14-pre.5 +``` + +Commit attendu : + +```text +v0.2.14-pre.005 +``` + +## 2. Objectif + +Fermer la surface Program API déjà matérialisée sans ajouter de contrat fonctionnel : + +```text +bounds et sécurité Debug/error +inventaire exact des exports publics +inventaire exact des modules de production +absence de closed-world Program enum +absence de registry/descriptors/preparer/payload D3 +absence de serde/codec/logging/runtime +associated output sans bound implicite +``` + +## 3. Release completeness + +Le nouveau `tests/release_completeness.rs` verrouille : + +```text +10 réexports crate-root exacts +aucun pub mod +3 fichiers Rust de production exacts +2 enums publics exacts, tous deux non_exhaustive +1 trait public exact : ProgramInstructionDecoder +aucun ProgramKind / registry / autre famille decoder / preparer +aucun Any / serde / codec / logging / réseau / IO runtime +aucun dyn ProgramInstructionDecoder ou registry hétérogène anticipé +``` + +La surface publique reste donc ouverte par `Pubkey` et associated output, pas par un inventaire central fermé. + +## 4. Hardening adversarial + +Le nouveau `tests/security_hardening.rs` couvre trois propriétés. + +### Input Interface maximal + +Une instruction au maximum déjà admis par Interface : + +```text +255 account metas +10 240 bytes de data +``` + +traverse `ProgramInstructionDecoder::decode` par référence et le decoder observe les tailles exactes. Program API n'introduit aucun second bound ni nouveau type d'input. + +### Payload hostile et erreur Core + +Un decoder de test reçoit un payload contenant un marqueur hostile puis retourne un `ksp_program_api::Error` sûr. Le contrat Program API ne copie automatiquement ni l'instruction ni ses bytes dans l'erreur, son `Display` ou son `Debug`. + +L'hygiène des messages/contextes qu'une implémentation tierce construit volontairement reste la responsabilité de cette implémentation ; Program API n'ajoute aucun canal `Failed` parallèle susceptible de dupliquer le payload. + +### Associated output sans bound implicite + +Un second decoder de test utilise un output contenant `Rc>`. Ce type n'implémente donc pas `Send`/`Sync` et aucun `Debug`/`Clone` n'est dérivé. La compilation prouve que `type Decoded` reste sans bound implicite ; le `Debug` de l'outcome demeure `Decoded` sans rendre la valeur. + +## 5. Dependency/runtime firewall + +Le manifest de production reste inchangé : + +```text +ksp-core-lib +ksp-interface-lib +``` + +Les canaris refusent toujours : + +```text +ksp-program-lib +ksp-logging-lib +Config / Transport / Wallet / Store / Materializer +serde / serde_json / Any +borsh / bincode / wincode +reqwest / tokio / tonic / tauri / tracing +std::env / std::fs / std::net dans la production Program API +``` + +## 6. Fichiers ajoutés + +```text +crates/ksp-program-api/tests/release_completeness.rs +crates/ksp-program-api/tests/security_hardening.rs +deltas/0.2.14/pre.005.md +``` + +## 7. Fichiers modifiés + +```text +Cargo.toml +docs/plans/021-V0_2_14_PROGRAM_API_PLAN.md +docs/validation/017-V0_2_14_PROGRAM_API.md +``` + +## 8. Fichiers volontairement inchangés + +```text +crates/ksp-program-api/Cargo.toml +crates/ksp-program-api/src/** +crates/ksp-program-api/README.md +crates/ksp-program-api/USAGE.md +crates/ksp-program-api/tests/dependency_boundary.rs +crates/ksp-program-api/tests/external_implementation.rs +crates/ksp-program-api/tests/public_api.rs +README.md +RULES.md +ROADMAP.md +CHANGELOG.md +docs/architecture/** +docs/rules/** +crates/ksp-core-lib/** +crates/ksp-interface-lib/** +crates/ksp-logging-lib/** +crates/ksp-*-transport-lib/** +crates/ksp-config-lib/** +crates/ksp-wallet-lib/** +crates/ksp-app-*/** +prompts/** +``` + +## 9. Scope négatif maintenu + +Cette tranche n'introduit pas : + +```text +nouveau type ou trait de production +runtime decoder registry +Vec> +object-safety heterogeneous promise +identity/version/coverage descriptor +priority/conflict policy +ProgramAccountDecoder / Event / ReturnData +payload canonique D3 +serde / JSON / Any +proof/confidence contextuels +ProgramExecutionPreparer +ExecutionPolicy / Execution +``` + +## 10. Gate opérateur attendu + +Après application : + +```bash +cargo fmt --all +python3 scripts/audit_rust_workspace_rules.py +python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.2.14 +cargo check --workspace +cargo clippy --workspace --all-targets +cargo test -p ksp-program-api +cargo test --workspace +cargo tree -p ksp-program-api --edges normal +cargo tree --duplicates +``` + +Le graphe normal doit rester : + +```text +ksp-program-api +├── ksp-core-lib +└── ksp-interface-lib + └── ksp-core-lib +``` + +Une correction découverte par ce gate reste un `0.2.14-pre.005-fix.NNN` et n'avance pas `pre.006`. + +## 11. Suite + +Après gate vert, `pre.006` est un **gate technique final sans développement fonctionnel**. Il ne doit ajouter ni contrat, ni decoder officiel, ni registry, ni preparer. diff --git a/docs/plans/021-V0_2_14_PROGRAM_API_PLAN.md b/docs/plans/021-V0_2_14_PROGRAM_API_PLAN.md index 1e80ada..238c8af 100644 --- a/docs/plans/021-V0_2_14_PROGRAM_API_PLAN.md +++ b/docs/plans/021-V0_2_14_PROGRAM_API_PLAN.md @@ -1,5 +1,5 @@ - + # Plan `0.2.14` — Program API foundation @@ -555,13 +555,15 @@ Aucun trait decoder, recognition, outcome, registry, codec, runtime logging ou e ### `pre.004` — `ProgramInstructionDecoder` + external implementation -**Statut : matérialisé ; gate opérateur à confirmer.** +**Statut : réalisé ; gate opérateur intégralement PASS.** Le trait `ProgramInstructionDecoder: Send + Sync` expose l'associated type `Decoded`, `program_ids`, `recognize` et `decode`. Un test d'intégration downstream-style l'implémente avec un type décodé tiers et un `Pubkey` explicitement absent du registry Core. L'implémentation ne requiert ni `ksp-program-lib`, ni enum centrale, ni `Any`, JSON ou codec. ### `pre.005` — Adversarial/API hardening + completeness -Verrouiller bounds/Debug/error safety, exact export inventory, absence de closed-world enum, absence de serde/codec/logging/runtime et scope négatif registry/preparer/payload canonique. +**Statut : matérialisé ; gate opérateur à confirmer.** + +Deux canaris de fermeture sont ajoutés : `release_completeness.rs` verrouille l'inventaire exact des exports/modules et l'absence de surface closed-world/runtime ; `security_hardening.rs` couvre input Interface maximal, erreur sûre sur payload hostile et associated output sans bound implicite. Aucun contrat fonctionnel n'est ajouté. ### `pre.006` — Gate technique final @@ -649,6 +651,30 @@ Le canari externe utilise uniquement la façade `ksp_program_api::*` pour l'impl `pre.005` reste une tranche de hardening/completeness : elle ne doit pas élargir le contrat fonctionnel. +## 13.4 État préparé après `pre.005` + +Le gate opérateur `pre.004` fourni le 28 août 2026 est intégralement vert : audits Rust/Markdown, check, Clippy, tests ciblés, workspace complet, canari externe et graphes Cargo passent. + +La tranche `pre.005` ajoute uniquement des preuves de fermeture : + +```text +exact crate-root exports 10 exports explicitement verrouillés +production modules lib + decode vocabulary + decoder trait uniquement +public enums Recognition + DecodeOutcome uniquement +public traits ProgramInstructionDecoder uniquement +closed-world Program enum absent +registry / descriptors / preparer absents +serde / JSON / Any / codecs absents +logging / runtime / IO absents +max Interface instruction consommable par référence +malformed hostile payload Err Core sûr sans copie automatique du payload +associated Decoded bounds aucun bound implicite ajouté +dyn heterogeneous registry aucune promesse +normal dependencies inchangées : Core + Interface +``` + +`pre.006` reste un gate technique final sans développement fonctionnel. + ## 14. Hors périmètre confirmé ```text diff --git a/docs/validation/017-V0_2_14_PROGRAM_API.md b/docs/validation/017-V0_2_14_PROGRAM_API.md index 919f9ac..60f068a 100644 --- a/docs/validation/017-V0_2_14_PROGRAM_API.md +++ b/docs/validation/017-V0_2_14_PROGRAM_API.md @@ -1,5 +1,5 @@ - + # Validation `0.2.14` — Program API foundation @@ -66,7 +66,7 @@ aucun ksp-program-lib aucun module privé ``` -Ce canari remplace toute affirmation documentaire non exécutable d'extensibilité. Son gate opérateur reste à confirmer avant de déclarer la tranche close. +Ce canari remplace toute affirmation documentaire non exécutable d'extensibilité. Son gate opérateur `pre.004` est confirmé intégralement vert. ## 5. Dependency firewall cible @@ -103,8 +103,8 @@ tracing | Gate | Attendu | Statut initial | |---------------------------|--------------------------------------------------------------------------|----------------| | unknown Program Pubkey | utilisable sans registry Core | PASS `pre.004` | -| max Interface input | decoder consomme l'input déjà borné sans nouvelle allocation obligatoire | PENDING | -| malformed program payload | `Err` ou `Unsupported` selon contrat, sans payload dans l'erreur | PENDING | +| max Interface input | decoder consomme l'input déjà borné sans nouvelle allocation obligatoire | PASS `pre.005` | +| malformed program payload | `Err` ou `Unsupported`; aucun echo n'est ajouté par le contrat | PASS `pre.005` | | Debug recognition | aucun payload | PASS | | Debug outcome | contenu `Decoded` non rendu automatiquement | PASS | | default methods | aucun default method susceptible de masquer panic/policy | PASS `pre.004` | @@ -225,4 +225,46 @@ Ce gate autorise l'ouverture de `pre.004`. | registry / descriptor / payload D3 | ABSENT | hors scope maintenu | | serde / codec / logging / runtime | ABSENT | dependency firewall inchangé | -Le canari d'implémentation consomme la façade `ksp_program_api` pour le trait, les types et les outcomes. Le registry Core n'est utilisé que par l'assertion de test négative et n'est pas réexporté par Program API. Le gate opérateur de cet overlay reste requis avant `pre.005`. +Le canari d'implémentation consomme la façade `ksp_program_api` pour le trait, les types et les outcomes. Le registry Core n'est utilisé que par l'assertion de test négative et n'est pas réexporté par Program API. Le gate opérateur de `pre.004` est confirmé intégralement vert et autorise `pre.005`. + +## 12. Gate opérateur `pre.004` + +Le gate fourni le 28 août 2026 est intégralement vert : + +```text +cargo fmt --all PASS +audit Rust général / exports / workspace PASS +audit Markdown PASS — 172 tables / 121 fichiers +cargo check --workspace PASS +cargo clippy --workspace --all-targets PASS +cargo test -p ksp-program-api PASS — 11 tests Rust +cargo test --workspace PASS +cargo tree -p ksp-program-api --edges normal PASS — Core + Interface uniquement +cargo tree --duplicates exécuté, inventaire workspace observé +``` + +Ce gate autorise l'ouverture de `pre.005`. + +## 13. État préparé `pre.005` + +| Critère | Statut | Preuve | +|--------------------------------------|--------|----------------------------------------------------------------------| +| exact crate-root export inventory | PASS | test `release_completeness` sur les 10 réexports | +| exact production module inventory | PASS | `lib.rs`, decode vocabulary et decoder trait uniquement | +| public enum inventory | PASS | Recognition + DecodeOutcome uniquement | +| closed-world Program enum | ABSENT | aucun `ProgramKind`/inventaire public central | +| registry / descriptors | ABSENT | aucune collection/runtime selection/identity-version-coverage | +| `ProgramExecutionPreparer` | ABSENT | scope négatif maintenu | +| serde / JSON / Any / codecs | ABSENT | manifest + source canaries | +| logging / runtime / IO | ABSENT | aucune dépendance ou primitive runtime/FS/env/network | +| max Interface input | PASS | 255 accounts + 10 240 bytes traversent le trait par référence | +| malformed hostile payload | PASS | `Result::Err` Core reste sûr sans echo automatique du payload | +| Debug outcome | PASS | valeur décodée jamais formatée | +| associated `Decoded` implicit bounds | ABSENT | canari avec output `Rc>`, donc non-`Send`/non-`Sync` accepté | +| dyn heterogeneous claim | ABSENT | aucun `dyn ProgramInstructionDecoder`/registry | +| dépendances normales | PASS | `ksp-core-lib` + `ksp-interface-lib` uniquement | + +La sécurité des messages/contextes produits volontairement par une implémentation tierce reste sa responsabilité ; `ksp-program-api` garantit seulement qu'il n'ajoute aucun canal parallèle ni copie automatique du payload hostile. + +Le gate opérateur de `pre.005` doit confirmer ces canaris avant le gate technique final `pre.006`. +