v0.2.14-pre.005

This commit is contained in:
2026-08-28 15:12:59 +02:00
parent bcf2f16c05
commit 0319794e59
6 changed files with 589 additions and 10 deletions

View File

@@ -1,12 +1,12 @@
# file: Cargo.toml # file: Cargo.toml
# version: 316 # version: 317
[workspace] [workspace]
resolver = "3" resolver = "3"
members = ["crates/ksp-app-config-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-interface-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-program-api", "crates/ksp-wallet-lib"] members = ["crates/ksp-app-config-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-interface-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-program-api", "crates/ksp-wallet-lib"]
[workspace.package] [workspace.package]
version = "0.2.14-pre.4" version = "0.2.14-pre.5"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project" repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"

View File

@@ -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<Decoded> {", "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<Decoded>"));
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;
}

View File

@@ -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<ksp_program_api::ProgramInstructionDecodeOutcome<Self::Decoded>> {
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<std::cell::Cell<u8>>,
}
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<ksp_program_api::ProgramInstructionDecodeOutcome<Self::Decoded>> {
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;
}

217
deltas/0.2.14/pre.005.md Normal file
View File

@@ -0,0 +1,217 @@
<!-- file: deltas/0.2.14/pre.005.md -->
<!-- version: 1 -->
# 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<Cell<u8>>`. 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<Box<dyn ProgramInstructionDecoder>>
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.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/021-V0_2_14_PROGRAM_API_PLAN.md --> <!-- file: docs/plans/021-V0_2_14_PROGRAM_API_PLAN.md -->
<!-- version: 4 --> <!-- version: 5 -->
# Plan `0.2.14` — Program API foundation # 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 ### `pre.004` — `ProgramInstructionDecoder` + external implementation
**Statut : matérialisé ; gate opérateur à confirmer.** **Statut : 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. 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 ### `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 ### `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. `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é ## 14. Hors périmètre confirmé
```text ```text

View File

@@ -1,5 +1,5 @@
<!-- file: docs/validation/017-V0_2_14_PROGRAM_API.md --> <!-- file: docs/validation/017-V0_2_14_PROGRAM_API.md -->
<!-- version: 4 --> <!-- version: 5 -->
# Validation `0.2.14` — Program API foundation # Validation `0.2.14` — Program API foundation
@@ -66,7 +66,7 @@ aucun ksp-program-lib
aucun module privé 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 ## 5. Dependency firewall cible
@@ -103,8 +103,8 @@ tracing
| Gate | Attendu | Statut initial | | Gate | Attendu | Statut initial |
|---------------------------|--------------------------------------------------------------------------|----------------| |---------------------------|--------------------------------------------------------------------------|----------------|
| unknown Program Pubkey | utilisable sans registry Core | PASS `pre.004` | | 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 | | max Interface input | decoder consomme l'input déjà borné sans nouvelle allocation obligatoire | PASS `pre.005` |
| malformed program payload | `Err` ou `Unsupported` selon contrat, sans payload dans l'erreur | PENDING | | malformed program payload | `Err` ou `Unsupported`; aucun echo n'est ajouté par le contrat | PASS `pre.005` |
| Debug recognition | aucun payload | PASS | | Debug recognition | aucun payload | PASS |
| Debug outcome | contenu `Decoded` non rendu automatiquement | PASS | | Debug outcome | contenu `Decoded` non rendu automatiquement | PASS |
| default methods | aucun default method susceptible de masquer panic/policy | PASS `pre.004` | | 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 | | registry / descriptor / payload D3 | ABSENT | hors scope maintenu |
| serde / codec / logging / runtime | ABSENT | dependency firewall inchangé | | 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<Cell<_>>`, 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`.