v0.4.7-pre.010
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user