v0.4.7-pre.010

This commit is contained in:
2026-08-02 19:22:14 +02:00
parent ebdfd30d5d
commit b6a90be1e4
11 changed files with 628 additions and 20 deletions

View File

@@ -1,13 +1,18 @@
<!-- file: kb-pipeline-demo-scenarios/CHANGELOG.md -->
<!-- version: 10 -->
<!-- version: 11 -->
# CHANGELOG — kb-pipeline-demo-scenarios
## 0.4.7-pre.010
- ajout dun corpus fermé de validation croisée Metaplex couvrant outer/CPI, succès/échec, cinq familles dactifs et neuf classes derreur ;
- ajout de trois contrats de preuve réseau distincts pour simulation, soumission et postcondition ;
- refus machine-readable de toute promotion réseau fondée uniquement sur des preuves synthétiques.
## 0.4.7-pre.009-fix-001
- ajout de linventaire Metaplex Devnet simulation et de son test de couverture.
# CHANGELOG — kb-pipeline-demo-scenarios
## 0.4.7-pre.008
- ajout de linventaire synthétique Metaplex NFT, SFT, fungible, collection et pNFT ;

View File

@@ -1,5 +1,5 @@
<!-- file: kb-pipeline-demo-scenarios/README.md -->
<!-- version: 2 -->
<!-- version: 3 -->
# kb-pipeline-demo-scenarios
@@ -18,6 +18,7 @@ La crate est mixte :
- préparation de fixtures publiques ;
- validation des matrices et rapports Token-2022 ;
- scénarios synthétiques et matrice de validation Metaplex Token Metadata ;
- corpus fermé de validation croisée outer/CPI, cas positifs/négatifs et preuves réseau exactes ;
- progression observable et annulation coopérative.
Elle ne remplace ni le pipeline générique ni lapplication desktop.

View File

@@ -1,10 +1,11 @@
<!-- file: kb-pipeline-demo-scenarios/TODO.md -->
<!-- version: 3 -->
<!-- version: 4 -->
# TODO — kb-pipeline-demo-scenarios
## `0.4.7`
- [x] Corpus croisé Metaplex - fermer les chemins outer/CPI, cinq familles dactifs, échecs transactionnels et entrées invalides.
- [ ] Tests réseau Metaplex - exécuter les validations Devnet/Testnet lorsque les fixtures, comptes et fonds nécessaires sont disponibles.
- [ ] Preuves Metaplex - renseigner les signatures, simulations et postconditions observées sans déclarer de validation réseau non exécutée.

View File

@@ -1,5 +1,5 @@
<!-- file: kb-pipeline-demo-scenarios/USAGE.md -->
<!-- version: 3 -->
<!-- version: 4 -->
# Utilisation de kb-pipeline-demo-scenarios
@@ -220,3 +220,17 @@ assert!(scenarios.iter().all(|scenario| {
```
Cet inventaire prépare les tests et campagnes Devnet simulation-first. Il ne constitue pas à lui seul une validation réseau réussie.
## Charger le corpus de validation croisée Metaplex
```rust
fn load_metaplex_cross_validation() -> kb_core::Result<usize> {
let matrix = kb_pipeline_demo_scenarios::load_metaplex_token_metadata_cross_validation_matrix();
return match matrix {
Ok(value) => Ok(value.cases.len()),
Err(error) => Err(error),
};
}
```
Les cas marqués `requiresNetworkEvidence` ne peuvent pas être déclarés validés à partir de preuves synthétiques. Une simulation, une soumission ou une confirmation exige les preuves RPC déclarées par le cas.

View File

@@ -1,5 +1,5 @@
// file: kb-pipeline-demo-scenarios/src/lib.rs
// version: 7
// version: 8
#![forbid(unsafe_code)]
#![deny(unreachable_pub)]
@@ -79,6 +79,16 @@ pub use self::solana_metaplex_token_metadata_scenarios::metaplex_token_metadata_
pub use self::solana_metaplex_token_metadata_scenarios::metaplex_token_metadata_synthetic_scenarios;
/// Maximum evidence entries accepted by one Metaplex validation scenario.
pub use self::solana_metaplex_token_metadata_validation::MAX_METAPLEX_TOKEN_METADATA_VALIDATION_EVIDENCE;
/// One case in the closed Metaplex cross-validation corpus.
pub use self::solana_metaplex_token_metadata_validation::MetaplexTokenMetadataCrossValidationCase;
/// Closed Metaplex cross-validation corpus for `0.4.7-pre.010`.
pub use self::solana_metaplex_token_metadata_validation::MetaplexTokenMetadataCrossValidationMatrix;
/// Stable failure category exercised by one negative case.
pub use self::solana_metaplex_token_metadata_validation::MetaplexTokenMetadataFailureClass;
/// Expected transaction outcome for one cross-validation case.
pub use self::solana_metaplex_token_metadata_validation::MetaplexTokenMetadataTransactionOutcome;
/// Transaction location covered by one cross-validation case.
pub use self::solana_metaplex_token_metadata_validation::MetaplexTokenMetadataTransactionPath;
/// One bounded Metaplex validation evidence entry.
pub use self::solana_metaplex_token_metadata_validation::MetaplexTokenMetadataValidationEvidence;
/// Canonical Metaplex validation matrix.
@@ -87,8 +97,12 @@ pub use self::solana_metaplex_token_metadata_validation::MetaplexTokenMetadataVa
pub use self::solana_metaplex_token_metadata_validation::MetaplexTokenMetadataValidationMatrixScenario;
/// Exact validation status for one Metaplex scenario.
pub use self::solana_metaplex_token_metadata_validation::MetaplexTokenMetadataValidationStatus;
/// Loads and validates the closed Metaplex cross-validation corpus.
pub use self::solana_metaplex_token_metadata_validation::load_metaplex_token_metadata_cross_validation_matrix;
/// Loads and validates the canonical Metaplex validation matrix.
pub use self::solana_metaplex_token_metadata_validation::load_metaplex_token_metadata_validation_matrix;
/// Validates the closed Metaplex cross-validation corpus and evidence claims.
pub use self::solana_metaplex_token_metadata_validation::validate_metaplex_token_metadata_cross_validation_matrix;
/// Validates the canonical Metaplex validation matrix.
pub use self::solana_metaplex_token_metadata_validation::validate_metaplex_token_metadata_validation_matrix;
/// Complete request for one Devnet Token-2022 execution.

View File

@@ -1,5 +1,5 @@
// file: kb-pipeline-demo-scenarios/src/solana_metaplex_token_metadata_validation.rs
// version: 1
// version: 2
//! Machine-readable Metaplex Token Metadata validation contract.
@@ -64,6 +64,203 @@ pub struct MetaplexTokenMetadataValidationMatrix {
pub scenarios: std::vec::Vec<crate::MetaplexTokenMetadataValidationMatrixScenario>,
}
/// Transaction location covered by one cross-validation case.
#[derive(
Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Deserialize, serde::Serialize,
)]
#[serde(rename_all = "snake_case")]
pub enum MetaplexTokenMetadataTransactionPath {
/// Top-level instruction.
Outer,
/// Cross-program invocation.
Cpi,
}
/// Expected transaction outcome for one cross-validation case.
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub enum MetaplexTokenMetadataTransactionOutcome {
/// Successful transaction path.
Success,
/// Failed or rejected transaction path.
Failed,
}
/// Stable failure category exercised by one negative case.
#[derive(
Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Deserialize, serde::Serialize,
)]
#[serde(rename_all = "snake_case")]
pub enum MetaplexTokenMetadataFailureClass {
/// Transaction failed before committed observations could be emitted.
TransactionFailed,
/// Program ID mismatch.
ProgramId,
/// Account owner mismatch.
Owner,
/// Program-derived address mismatch.
Pda,
/// Positional account or flag mismatch.
Accounts,
/// Truncated instruction payload.
PayloadTruncated,
/// Forbidden trailing instruction bytes.
PayloadSuffix,
/// Unknown instruction discriminant.
UnknownDiscriminant,
/// Conflicting Metaplex and Token-2022 metadata sources.
MetadataSourceConflict,
}
/// One case in the closed Metaplex cross-validation corpus.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MetaplexTokenMetadataCrossValidationCase {
/// Stable case identifier.
pub id: std::string::String,
/// Outer or CPI transaction path.
pub path: crate::MetaplexTokenMetadataTransactionPath,
/// Expected transaction outcome.
pub outcome: crate::MetaplexTokenMetadataTransactionOutcome,
/// Optional asset family for successful corpus cases.
pub asset_family: std::option::Option<crate::MetaplexTokenMetadataAssetFamily>,
/// Optional stable negative-case category.
pub failure_class: std::option::Option<crate::MetaplexTokenMetadataFailureClass>,
/// Whether status changes require actual RPC evidence.
pub requires_network_evidence: bool,
/// Exact observed validation status.
pub status: crate::MetaplexTokenMetadataValidationStatus,
/// Evidence kinds required before the declared status is accepted.
pub required_evidence: std::vec::Vec<std::string::String>,
/// Bounded observed evidence.
#[serde(default)]
pub evidence: std::vec::Vec<crate::MetaplexTokenMetadataValidationEvidence>,
}
/// Closed Metaplex cross-validation corpus for `0.4.7-pre.010`.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MetaplexTokenMetadataCrossValidationMatrix {
/// Matrix schema version.
pub matrix_version: u32,
/// Exact owning prerelease.
pub milestone: std::string::String,
/// Ordered validation cases.
pub cases: std::vec::Vec<crate::MetaplexTokenMetadataCrossValidationCase>,
}
/// Loads and validates the closed Metaplex cross-validation corpus.
pub fn load_metaplex_token_metadata_cross_validation_matrix()
-> kb_core::Result<crate::MetaplexTokenMetadataCrossValidationMatrix> {
let parsed = match serde_json::from_str::<crate::MetaplexTokenMetadataCrossValidationMatrix>(
include_str!(
"../../test-fixtures/contract-matrices/METAPLEX_TOKEN_METADATA_CROSS_VALIDATION_MATRIX.json"
),
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(kb_core::Error::new(
"metaplex_token_metadata_cross_validation_matrix_invalid_json",
format!("Metaplex cross-validation matrix JSON is invalid: {error}"),
));
},
};
if let std::result::Result::Err(error) =
validate_metaplex_token_metadata_cross_validation_matrix(&parsed)
{
return std::result::Result::Err(error);
}
return std::result::Result::Ok(parsed);
}
/// Validates the closed Metaplex cross-validation corpus and evidence claims.
pub fn validate_metaplex_token_metadata_cross_validation_matrix(
matrix: &crate::MetaplexTokenMetadataCrossValidationMatrix,
) -> kb_core::Result<()> {
if matrix.matrix_version != 1 || matrix.milestone != "0.4.7-pre.010" {
return std::result::Result::Err(kb_core::Error::new(
"metaplex_token_metadata_cross_validation_matrix_contract_mismatch",
"Metaplex cross-validation matrix must use version 1 for 0.4.7-pre.010",
));
}
let mut ids = std::collections::BTreeSet::<&str>::new();
for case in &matrix.cases {
if !ids.insert(case.id.as_str()) || case.required_evidence.is_empty() {
return std::result::Result::Err(kb_core::Error::new(
"metaplex_token_metadata_cross_validation_case_invalid",
"Metaplex cross-validation cases must be unique and declare evidence",
));
}
if case.evidence.len() > crate::MAX_METAPLEX_TOKEN_METADATA_VALIDATION_EVIDENCE {
return std::result::Result::Err(kb_core::Error::new(
"metaplex_token_metadata_cross_validation_evidence_limit_exceeded",
"Metaplex cross-validation evidence exceeds the compiled bound",
));
}
if case.outcome == crate::MetaplexTokenMetadataTransactionOutcome::Success
&& case.asset_family.is_none()
{
return std::result::Result::Err(kb_core::Error::new(
"metaplex_token_metadata_cross_validation_success_without_asset_family",
"Successful Metaplex corpus cases must identify an asset family",
));
}
if case.outcome == crate::MetaplexTokenMetadataTransactionOutcome::Failed
&& case.failure_class.is_none()
{
return std::result::Result::Err(kb_core::Error::new(
"metaplex_token_metadata_cross_validation_failure_without_class",
"Failed Metaplex corpus cases must identify a failure class",
));
}
if matches!(
case.status,
crate::MetaplexTokenMetadataValidationStatus::NotRun
| crate::MetaplexTokenMetadataValidationStatus::Unavailable
) && !case.evidence.is_empty()
{
return std::result::Result::Err(kb_core::Error::new(
"metaplex_token_metadata_cross_validation_unobserved_with_evidence",
"Not-run or unavailable cross-validation cases must not contain evidence",
));
}
if case.requires_network_evidence
&& matches!(
case.status,
crate::MetaplexTokenMetadataValidationStatus::SyntheticValidated
)
{
return std::result::Result::Err(kb_core::Error::new(
"metaplex_token_metadata_cross_validation_network_overdeclared",
"Network cases cannot be declared validated from synthetic evidence",
));
}
if matches!(
case.status,
crate::MetaplexTokenMetadataValidationStatus::Simulated
| crate::MetaplexTokenMetadataValidationStatus::Submitted
| crate::MetaplexTokenMetadataValidationStatus::Confirmed
) {
let observed = case
.evidence
.iter()
.map(|evidence| return evidence.kind.as_str())
.collect::<std::collections::BTreeSet<&str>>();
if !case
.required_evidence
.iter()
.all(|kind| return observed.contains(kind.as_str()))
{
return std::result::Result::Err(kb_core::Error::new(
"metaplex_token_metadata_cross_validation_status_without_required_evidence",
"Observed Metaplex network status lacks required evidence",
));
}
}
}
return std::result::Result::Ok(());
}
/// Loads and validates the canonical Metaplex validation matrix.
pub fn load_metaplex_token_metadata_validation_matrix()
-> kb_core::Result<crate::MetaplexTokenMetadataValidationMatrix> {
@@ -189,4 +386,55 @@ mod tests {
assert!(crate::validate_metaplex_token_metadata_validation_matrix(&matrix).is_ok());
}
}
#[test]
fn cross_validation_corpus_is_closed_and_covers_every_required_dimension() {
let result = crate::load_metaplex_token_metadata_cross_validation_matrix();
assert!(result.is_ok());
if let std::result::Result::Ok(matrix) = result {
assert_eq!(matrix.cases.len(), 19);
let paths = matrix
.cases
.iter()
.map(|case| return case.path)
.collect::<std::collections::BTreeSet<crate::MetaplexTokenMetadataTransactionPath>>(
);
assert_eq!(paths.len(), 2);
let families = matrix
.cases
.iter()
.filter_map(|case| return case.asset_family)
.collect::<std::collections::BTreeSet<crate::MetaplexTokenMetadataAssetFamily>>(
);
assert_eq!(families.len(), 5);
let failures = matrix
.cases
.iter()
.filter_map(|case| return case.failure_class)
.collect::<std::collections::BTreeSet<crate::MetaplexTokenMetadataFailureClass>>(
);
assert_eq!(failures.len(), 9);
assert_eq!(
matrix.cases.iter().filter(|case| return case.requires_network_evidence).count(),
3
);
}
}
#[test]
fn network_cases_cannot_be_promoted_from_synthetic_or_incomplete_evidence() {
let result = crate::load_metaplex_token_metadata_cross_validation_matrix();
assert!(result.is_ok());
if let std::result::Result::Ok(mut matrix) = result {
let network =
matrix.cases.iter_mut().find(|case| return case.requires_network_evidence);
assert!(network.is_some());
if let std::option::Option::Some(case) = network {
case.status = crate::MetaplexTokenMetadataValidationStatus::SyntheticValidated;
}
assert!(
crate::validate_metaplex_token_metadata_cross_validation_matrix(&matrix).is_err()
);
}
}
}