v0.2.13-pre.005

This commit is contained in:
2026-08-28 05:24:26 +02:00
parent 0ee28eeb95
commit 29f27ae109
9 changed files with 529 additions and 20 deletions

View File

@@ -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"

View File

@@ -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;
}

View 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;
}

View File

@@ -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;
}

View 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;
}

View File

@@ -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;
}

213
deltas/0.2.13/pre.005.md Normal file
View 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.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/020-V0_2_13_INTERFACE_PLAN.md -->
<!-- version: 4 -->
<!-- version: 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 : pvu**
**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.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/validation/016-V0_2_13_INTERFACE.md -->
<!-- version: 4 -->
<!-- version: 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.