v0.4.7-pre.012

This commit is contained in:
2026-08-03 09:34:16 +02:00
parent 4b727e2990
commit 4d08c75b2b
13 changed files with 575 additions and 36 deletions

View File

@@ -1,5 +1,5 @@
// file: kb-pipeline-demo-scenarios/src/lib.rs
// version: 9
// version: 10
#![forbid(unsafe_code)]
#![deny(unreachable_pub)]
@@ -72,8 +72,16 @@ pub use self::solana_memo_execution::execute_devnet_memo;
pub use self::solana_metaplex_token_metadata_devnet_execution::DevnetMetaplexTokenMetadataExecutionRequest;
/// Complete evidence produced by one real Devnet Metaplex Token Metadata execution.
pub use self::solana_metaplex_token_metadata_devnet_execution::DevnetMetaplexTokenMetadataExecutionSummary;
/// Creates one named creator unverification request without handwritten JSON.
pub use self::solana_metaplex_token_metadata_devnet_execution::devnet_metaplex_creator_unverify_request;
/// Creates one named creator verification request without handwritten JSON.
pub use self::solana_metaplex_token_metadata_devnet_execution::devnet_metaplex_creator_verify_request;
/// Executes one real Devnet Metaplex simulation or explicitly authorized submission.
pub use self::solana_metaplex_token_metadata_devnet_execution::execute_devnet_metaplex_token_metadata;
/// Returns a formatted JSON template for one named current operation.
pub use self::solana_metaplex_token_metadata_devnet_execution::metaplex_token_metadata_current_operation_json_template;
/// Returns the current operation names accepted by automatic Devnet campaigns.
pub use self::solana_metaplex_token_metadata_devnet_execution::metaplex_token_metadata_current_operation_names;
/// Simulates one current Metaplex operation against a real Devnet endpoint.
pub use self::solana_metaplex_token_metadata_devnet_execution::simulate_devnet_metaplex_token_metadata;
/// Stable asset family covered by one Metaplex scenario.

View File

@@ -1,5 +1,5 @@
// file: kb-pipeline-demo-scenarios/src/solana_metaplex_token_metadata_devnet_execution.rs
// version: 1
// version: 4
//! Real Devnet simulation and submission for current Metaplex Token Metadata operations.
@@ -42,6 +42,40 @@ impl crate::DevnetMetaplexTokenMetadataExecutionRequest {
};
}
/// Creates a conservative request from one serialized typed Metaplex operation.
///
/// This entry point lets automated Devnet campaigns and the desktop reuse the
/// complete executor surface without duplicating one Rust constructor per
/// operation. The JSON must deserialize to `ExMetaplexTokenMetadataOperation`.
pub fn from_operation_json(
intent_id: impl std::convert::Into<std::string::String>,
operation_json: &str,
) -> kb_core::Result<Self> {
if operation_json.contains("\"operation\":\"...\"")
|| operation_json.contains("\"operation\": \"...\"")
|| operation_json.trim() == "..."
{
return std::result::Result::Err(kb_core::Error::config(
"Metaplex operation JSON still contains the documentation placeholder `...`; use metaplex_token_metadata_current_operation_json_template or one of the named request constructors",
));
}
let operation = match serde_json::from_str::<kb_lib::ExMetaplexTokenMetadataOperation>(
operation_json,
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(kb_core::Error::config(format!(
"invalid typed Metaplex operation JSON: {error}"
)));
},
};
let request = Self::new(intent_id, operation);
if let std::result::Result::Err(error) = request.validate() {
return std::result::Result::Err(error);
}
return std::result::Result::Ok(request);
}
/// Validates request-local bounds and rejects deprecated operations.
pub fn validate(&self) -> kb_core::Result<()> {
if self.intent_id.trim().is_empty() {
@@ -73,6 +107,164 @@ impl crate::DevnetMetaplexTokenMetadataExecutionRequest {
}
}
/// Returns the current operation names accepted by automatic Devnet campaigns.
pub fn metaplex_token_metadata_current_operation_names() -> &'static [&'static str] {
return &[
"create",
"update_as_update_authority_v2",
"verify",
"unverify",
"burn",
"delegate",
"revoke",
"lock",
"unlock",
"transfer",
"close_accounts",
"create_escrow_account",
"close_escrow_account",
"transfer_out_of_escrow",
"mint",
"migrate",
"use",
"collect",
"print",
"resize",
];
}
/// Returns a formatted JSON template for the first named Devnet campaign operations.
///
/// Placeholders beginning with `<` must be replaced before parsing or execution.
pub fn metaplex_token_metadata_current_operation_json_template(
operation: &str,
) -> kb_core::Result<std::string::String> {
let value = match operation {
"create" => serde_json::json!({
"operation": "create",
"metadata": "<METADATA_PDA>",
"master_edition": "<MASTER_EDITION_PDA_OR_NULL>",
"mint": "<MINT>",
"mint_as_signer": false,
"authority": "<MINT_AUTHORITY>",
"update_authority": "<UPDATE_AUTHORITY>",
"update_authority_as_signer": true,
"spl_token_program": null,
"create_args": {
"V1": {
"name": "Khadhroony Devnet Asset",
"symbol": "KHDEV",
"uri": "https://example.invalid/khadhroony-devnet.json",
"seller_fee_basis_points": 0,
"creators": null,
"primary_sale_happened": false,
"is_mutable": true,
"token_standard": "NonFungible",
"collection": null,
"uses": null,
"collection_details": null,
"rule_set": null,
"decimals": null,
"print_supply": null
}
}
}),
"update_as_update_authority_v2" => serde_json::json!({
"operation": "update_as_update_authority_v2",
"authority": "<UPDATE_AUTHORITY>",
"mint": "<MINT>",
"metadata": "<METADATA_PDA>",
"edition": null,
"token": null,
"authorization_rules_program": null,
"authorization_rules": null,
"update_args": {
"AsUpdateAuthorityV2": {
"new_update_authority": null,
"data": null,
"primary_sale_happened": null,
"is_mutable": null,
"collection": null,
"collection_details": null,
"uses": null,
"rule_set": null,
"authorization_data": null
}
}
}),
"verify_creator" | "verify" => serde_json::json!({
"operation": "verify",
"authority": "<CREATOR_AUTHORITY>",
"delegate_record": null,
"metadata": "<METADATA_PDA>",
"collection_mint": null,
"collection_metadata": null,
"collection_master_edition": null,
"verification_args": "CreatorV1"
}),
"unverify_creator" | "unverify" => serde_json::json!({
"operation": "unverify",
"authority": "<CREATOR_AUTHORITY>",
"delegate_record": null,
"metadata": "<METADATA_PDA>",
"collection_mint": null,
"collection_metadata": null,
"verification_args": "CreatorV1"
}),
_ => {
return std::result::Result::Err(kb_core::Error::config(format!(
"no named JSON template for `{operation}`; current operations: {}",
metaplex_token_metadata_current_operation_names().join(", ")
)));
},
};
return match serde_json::to_string_pretty(&value) {
std::result::Result::Ok(value) => std::result::Result::Ok(value),
std::result::Result::Err(error) => std::result::Result::Err(kb_core::Error::json(format!(
"unable to serialize Metaplex operation template: {error}"
))),
};
}
/// Creates one named creator verification request without handwritten operation JSON.
pub fn devnet_metaplex_creator_verify_request(
intent_id: impl std::convert::Into<std::string::String>,
authority: kb_lib::MdPubkey,
metadata: kb_lib::MdPubkey,
) -> crate::DevnetMetaplexTokenMetadataExecutionRequest {
return crate::DevnetMetaplexTokenMetadataExecutionRequest::new(
intent_id,
kb_lib::ExMetaplexTokenMetadataOperation::Verify {
authority,
delegate_record: std::option::Option::None,
metadata,
collection_mint: std::option::Option::None,
collection_metadata: std::option::Option::None,
collection_master_edition: std::option::Option::None,
verification_args: mpl_token_metadata::types::VerificationArgs::CreatorV1,
},
);
}
/// Creates one named creator unverification request without handwritten operation JSON.
pub fn devnet_metaplex_creator_unverify_request(
intent_id: impl std::convert::Into<std::string::String>,
authority: kb_lib::MdPubkey,
metadata: kb_lib::MdPubkey,
) -> crate::DevnetMetaplexTokenMetadataExecutionRequest {
return crate::DevnetMetaplexTokenMetadataExecutionRequest::new(
intent_id,
kb_lib::ExMetaplexTokenMetadataOperation::Unverify {
authority,
delegate_record: std::option::Option::None,
metadata,
collection_mint: std::option::Option::None,
collection_metadata: std::option::Option::None,
verification_args: mpl_token_metadata::types::VerificationArgs::CreatorV1,
},
);
}
/// Complete evidence produced by one real Devnet Metaplex execution.
#[derive(Clone, Debug, PartialEq)]
pub struct DevnetMetaplexTokenMetadataExecutionSummary {
@@ -583,6 +775,34 @@ fn validate_profile_wallet_signers(
#[cfg(test)]
mod tests {
fn example_devnet_profile() -> kb_config::ProfileConfig {
let config =
match kb_config::parse_config_json(include_str!("../../config/example.config.json")) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("example config parse failed: {error}"),
};
return match crate::resolve_demo_devnet_profile(&config, std::option::Option::None) {
std::result::Result::Ok(profile) => profile,
std::result::Result::Err(error) => panic!("Devnet profile resolution failed: {error}"),
};
}
fn optional_stateful_reads_from_env(
variable: &str,
) -> std::vec::Vec<kb_pipeline::MetaplexTokenMetadataStatefulReadRequest> {
let value = match std::env::var(variable) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return std::vec::Vec::new(),
};
return match serde_json::from_str::<
std::vec::Vec<kb_pipeline::MetaplexTokenMetadataStatefulReadRequest>,
>(value.as_str())
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("invalid {variable}: {error}"),
};
}
#[test]
#[allow(deprecated)]
fn request_rejects_deprecated_operations_and_empty_ids() {
@@ -598,6 +818,133 @@ mod tests {
assert!(empty.validate().is_err());
}
#[test]
fn typed_operation_json_rejects_invalid_and_deprecated_payloads() {
assert!(
crate::DevnetMetaplexTokenMetadataExecutionRequest::from_operation_json(
"invalid-json",
"{}",
)
.is_err()
);
let deprecated = serde_json::json!({
"operation": "puff_metadata",
"metadata": "11111111111111111111111111111111"
});
assert!(
crate::DevnetMetaplexTokenMetadataExecutionRequest::from_operation_json(
"deprecated-operation",
deprecated.to_string().as_str(),
)
.is_err()
);
}
#[test]
fn named_templates_are_json_and_placeholder_is_rejected_explicitly() {
for operation in ["create", "update_as_update_authority_v2", "verify", "unverify"] {
let template =
super::metaplex_token_metadata_current_operation_json_template(operation);
assert!(template.is_ok());
let template = match template {
std::result::Result::Ok(value) => value,
std::result::Result::Err(_) => return,
};
assert!(serde_json::from_str::<serde_json::Value>(template.as_str()).is_ok());
}
let placeholder = crate::DevnetMetaplexTokenMetadataExecutionRequest::from_operation_json(
"placeholder",
r#"{"operation":"..."}"#,
);
assert_eq!(
placeholder.as_ref().err().map(kb_core::Error::code),
std::option::Option::Some("config"),
);
}
#[test]
fn named_creator_requests_are_conservative_and_current() {
let authority = kb_lib::MdPubkey("11111111111111111111111111111111".to_string());
let metadata = kb_lib::MdPubkey("SysvarC1ock11111111111111111111111111111111".to_string());
let verify = super::devnet_metaplex_creator_verify_request(
"verify-creator",
authority.clone(),
metadata.clone(),
);
let unverify = super::devnet_metaplex_creator_unverify_request(
"unverify-creator",
authority,
metadata,
);
assert!(!verify.submit);
assert!(!verify.operator_confirmed);
assert!(verify.validate().is_ok());
assert!(unverify.validate().is_ok());
}
#[tokio::test]
async fn optional_devnet_current_operation_from_env() {
if std::env::var("KB_DEVNET_METAPLEX_EXECUTION_TEST").ok().as_deref()
!= std::option::Option::Some("1")
{
return;
}
let operation_json = match std::env::var("KB_DEVNET_METAPLEX_OPERATION_JSON") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
panic!("KB_DEVNET_METAPLEX_OPERATION_JSON is required: {error}");
},
};
let mut profile = example_devnet_profile();
if let std::result::Result::Ok(directory) = std::env::var("KB_DEVNET_WALLET_DIR") {
profile.wallet.wallet_dir = directory;
}
let pool = match kb_onchain_transport::HttpEndpointPool::from_profile(&profile) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("HTTP pool creation failed: {error}"),
};
let workspace_root = match std::path::Path::new(env!("CARGO_MANIFEST_DIR")).parent() {
std::option::Option::Some(value) => value,
std::option::Option::None => panic!("workspace root cannot be resolved"),
};
let mut request =
match crate::DevnetMetaplexTokenMetadataExecutionRequest::from_operation_json(
format!("devnet-metaplex-test-{}", uuid::Uuid::new_v4()),
operation_json.as_str(),
) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
panic!("Metaplex request parsing failed: {error}")
},
};
request.preflight_reads =
optional_stateful_reads_from_env("KB_DEVNET_METAPLEX_PREFLIGHT_READS_JSON");
request.postcondition_reads =
optional_stateful_reads_from_env("KB_DEVNET_METAPLEX_POSTCONDITION_READS_JSON");
request.submit = std::env::var("KB_DEVNET_METAPLEX_SUBMIT").ok().as_deref()
== std::option::Option::Some("1");
request.operator_confirmed = request.submit
&& std::env::var("KB_DEVNET_METAPLEX_OPERATOR_CONFIRMED").ok().as_deref()
== std::option::Option::Some("1");
let summary = match crate::execute_devnet_metaplex_token_metadata(
&pool,
&profile,
workspace_root,
&request,
&crate::NoopSolanaExecutionObserver,
)
.await
{
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("Devnet Metaplex execution failed: {error}"),
};
assert!(summary.simulation.success, "{:#?}", summary.simulation);
if request.submit {
assert!(summary.send_result.is_some());
assert!(summary.confirmation.is_some());
}
}
#[test]
fn signer_guard_rejects_external_authorities() {
assert!(super::validate_profile_wallet_signers(&["wallet".to_string()], "wallet").is_ok());

View File

@@ -1,5 +1,5 @@
// file: kb-pipeline-demo-scenarios/src/solana_metaplex_token_metadata_validation.rs
// version: 3
// version: 4
//! Machine-readable Metaplex Token Metadata validation contract.
@@ -506,13 +506,13 @@ pub fn validate_metaplex_token_metadata_devnet_execution_matrix(
matrix: &crate::MetaplexTokenMetadataDevnetExecutionMatrix,
) -> kb_core::Result<()> {
if matrix.matrix_version != 1
|| matrix.milestone != "0.4.7-pre.011"
|| matrix.milestone != "0.4.7-pre.012"
|| matrix.program_id != kb_program_ids::METADATA_METAPLEX_TOKEN_METADATA_PROGRAM_ID
|| matrix.operations.len() != 20
{
return std::result::Result::Err(kb_core::Error::new(
"metaplex_devnet_execution_matrix_contract_mismatch",
"Metaplex Devnet matrix must declare exactly 20 current operations for pre.011",
"Metaplex Devnet matrix must declare exactly 20 current operations for pre.012",
));
}
let expected = [
@@ -552,6 +552,8 @@ pub fn validate_metaplex_token_metadata_devnet_execution_matrix(
}
for operation in &matrix.operations {
if operation.deprecated
|| operation.runner_status != "implemented"
|| !matches!(operation.campaign_prerelease.as_str(), "0.4.7-pre.011" | "0.4.7-pre.012")
|| operation.required_evidence.is_empty()
|| operation.submission_evidence.is_empty()
|| !matches!(
@@ -561,7 +563,7 @@ pub fn validate_metaplex_token_metadata_devnet_execution_matrix(
{
return std::result::Result::Err(kb_core::Error::new(
"metaplex_devnet_execution_matrix_entry_invalid",
"Metaplex Devnet entries must be current, evidenced and conservatively classified",
"Metaplex Devnet entries must be current, implemented, evidenced and conservatively classified",
));
}
}
@@ -588,7 +590,21 @@ mod devnet_execution_matrix_tests {
.count(),
4,
);
assert_eq!(
matrix
.operations
.iter()
.filter(|operation| return operation.campaign_prerelease == "0.4.7-pre.012")
.count(),
16,
);
assert!(matrix.operations.iter().all(|operation| return !operation.deprecated));
assert!(
matrix
.operations
.iter()
.all(|operation| return operation.runner_status == "implemented")
);
assert!(
matrix
.operations