Files
khadhroony-bot3/kb-pipeline/src/solana_token2022_validation.rs
2026-07-25 11:31:48 +02:00

441 lines
18 KiB
Rust

// file: kb-pipeline/src/solana_token2022_validation.rs
// version: 4
//! Machine-readable validation evidence contract for the Token-2022 milestone.
/// Maximum number of evidence records accepted in one validation report.
pub const MAX_TOKEN2022_VALIDATION_EVIDENCE: usize = 64;
/// Required validation environment for one scenario.
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Token2022ValidationEnvironment {
/// Deterministic offline fixtures and builder comparisons.
Offline,
/// A local validator under operator control.
Localnet,
/// Solana Devnet.
Devnet,
/// Scenario may run on Localnet or Devnet according to deployment availability.
LocalnetOrDevnet,
/// Mainnet observations without mutable execution.
MainnetObservation,
/// PostgreSQL persistence and replay validation.
Postgres,
/// Tauri application smoke validation.
Tauri,
}
/// Exact status of one validation scenario.
#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Token2022ValidationStatus {
/// Scenario is declared but has not been run.
NotRun,
/// Scenario was simulated without submission.
Simulated,
/// Scenario was submitted but confirmation evidence is incomplete.
Submitted,
/// Scenario was confirmed and its required postconditions passed.
Confirmed,
/// Scenario is not available in the selected environment.
Unavailable,
/// Scenario ran and failed.
Failed,
}
/// One bounded proof attached to a validation scenario.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct Token2022ValidationEvidence {
/// Stable evidence kind such as `signature`, `test_suite`, or `replay`.
pub kind: std::string::String,
/// Bounded evidence value.
pub value: std::string::String,
}
/// One declared Token-2022 validation scenario and its observed evidence.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct Token2022ValidationScenario {
/// Stable scenario identifier.
pub id: std::string::String,
/// Validation environment.
pub environment: crate::Token2022ValidationEnvironment,
/// Exact observed status.
pub status: crate::Token2022ValidationStatus,
/// Whether canonical hydration is required.
pub requires_canonical_hydration: bool,
/// Whether core extraction is required.
pub requires_core_extraction: bool,
/// Whether decode replay is required.
pub requires_decode_replay: bool,
/// Whether materialization is required.
pub requires_materialization: bool,
/// Whether a second idempotent replay is required.
pub requires_second_replay: bool,
/// Ordered evidence records.
pub evidence: std::vec::Vec<crate::Token2022ValidationEvidence>,
}
/// Machine-readable validation matrix loaded from the canonical JSON document.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Token2022ValidationMatrix {
/// Matrix schema version.
pub matrix_version: u32,
/// Milestone owning this matrix.
pub milestone: std::string::String,
/// Aggregate matrix status.
pub status: std::string::String,
/// Exact accepted status vocabulary.
pub status_vocabulary: std::vec::Vec<std::string::String>,
/// Scenarios in stable roadmap order.
pub scenarios: std::vec::Vec<crate::Token2022ValidationMatrixScenario>,
}
/// One scenario declared by the canonical validation matrix.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Token2022ValidationMatrixScenario {
/// Stable scenario identifier.
pub id: std::string::String,
/// Required execution environment.
pub environment: crate::Token2022ValidationEnvironment,
/// Exact observed status.
pub status: crate::Token2022ValidationStatus,
/// Evidence kinds required before this scenario may be confirmed.
pub required_evidence: std::vec::Vec<std::string::String>,
/// Observed bounded evidence.
#[serde(default)]
pub evidence: std::vec::Vec<crate::Token2022ValidationEvidence>,
}
/// Loads and validates the canonical Token-2022 validation matrix.
pub fn load_token2022_validation_matrix() -> kb_core::Result<crate::Token2022ValidationMatrix> {
let parsed = match serde_json::from_str::<crate::Token2022ValidationMatrix>(include_str!(
"../../docs/SPL_TOKEN2022_VALIDATION_MATRIX.json"
)) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => {
return std::result::Result::Err(kb_core::Error::new(
"token2022_validation_matrix_invalid_json",
format!("Token-2022 validation matrix JSON is invalid: {error}"),
));
},
};
if let std::result::Result::Err(error) = validate_token2022_validation_matrix(&parsed) {
return std::result::Result::Err(error);
}
return std::result::Result::Ok(parsed);
}
/// Validates schema, scenario inventory, statuses, and observed evidence.
pub fn validate_token2022_validation_matrix(
matrix: &crate::Token2022ValidationMatrix,
) -> kb_core::Result<()> {
if matrix.matrix_version != 2 || matrix.milestone != "0.4.6" {
return std::result::Result::Err(kb_core::Error::new(
"token2022_validation_matrix_contract_mismatch",
"Token-2022 validation matrix must use version 2 for milestone 0.4.6",
));
}
let expected_statuses =
["not_run", "simulated", "submitted", "confirmed", "unavailable", "failed"];
let actual_statuses = matrix
.status_vocabulary
.iter()
.map(|status| return status.as_str())
.collect::<std::vec::Vec<&str>>();
if actual_statuses.as_slice() != expected_statuses {
return std::result::Result::Err(kb_core::Error::new(
"token2022_validation_matrix_status_vocabulary_mismatch",
"Token-2022 validation status vocabulary differs from the compiled contract",
));
}
let expected_ids = [
"offline_full_regression",
"token2022_public_devnet",
"elgamal_registry_devnet",
"confidential_transfer_localnet_or_devnet",
"confidential_mint_burn_localnet_or_devnet",
"permissioned_confidential_burn_localnet_or_devnet",
"mainnet_observation_corpus",
"postgres_double_replay",
"tauri_smoke",
];
let actual_ids = matrix
.scenarios
.iter()
.map(|scenario| return scenario.id.as_str())
.collect::<std::vec::Vec<&str>>();
if actual_ids.as_slice() != expected_ids {
return std::result::Result::Err(kb_core::Error::new(
"token2022_validation_matrix_scenario_inventory_mismatch",
"Token-2022 validation scenario inventory or order differs from the compiled contract",
));
}
for scenario in &matrix.scenarios {
if scenario.required_evidence.is_empty()
|| scenario.required_evidence.len() > crate::MAX_TOKEN2022_VALIDATION_EVIDENCE
{
return std::result::Result::Err(kb_core::Error::new(
"token2022_validation_matrix_required_evidence_invalid",
"Every Token-2022 validation scenario must declare bounded required evidence",
));
}
let mut required = std::collections::BTreeSet::<&str>::new();
for evidence_kind in &scenario.required_evidence {
if evidence_kind.trim().is_empty() || !required.insert(evidence_kind.as_str()) {
return std::result::Result::Err(kb_core::Error::new(
"token2022_validation_matrix_required_evidence_invalid",
"Required Token-2022 validation evidence must be non-empty and unique",
));
}
}
if scenario.status == crate::Token2022ValidationStatus::Confirmed {
let observed = scenario
.evidence
.iter()
.map(|evidence| return evidence.kind.as_str())
.collect::<std::collections::BTreeSet<&str>>();
if !required.iter().all(|kind| return observed.contains(kind)) {
return std::result::Result::Err(kb_core::Error::new(
"token2022_validation_matrix_confirmed_without_required_evidence",
format!(
"Confirmed Token-2022 validation scenario {} lacks required evidence",
scenario.id
),
));
}
} else if matches!(
scenario.status,
crate::Token2022ValidationStatus::NotRun
| crate::Token2022ValidationStatus::Unavailable
) && !scenario.evidence.is_empty()
{
return std::result::Result::Err(kb_core::Error::new(
"token2022_validation_matrix_unobserved_with_evidence",
"Not-run or unavailable Token-2022 scenarios must not retain observed evidence",
));
}
}
return std::result::Result::Ok(());
}
/// Complete validation report checked before milestone closure.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct Token2022ValidationReport {
/// Scenarios in stable roadmap order.
pub scenarios: std::vec::Vec<crate::Token2022ValidationScenario>,
}
/// Validates that a milestone report is bounded, unique, and does not overclaim evidence.
pub fn validate_token2022_validation_report(
report: &crate::Token2022ValidationReport,
) -> kb_core::Result<()> {
if report.scenarios.is_empty() {
return std::result::Result::Err(kb_core::Error::config(
"Token-2022 validation report must contain at least one scenario",
));
}
let mut ids = std::collections::BTreeSet::<std::string::String>::new();
for scenario in &report.scenarios {
if scenario.id.trim().is_empty() || !ids.insert(scenario.id.clone()) {
return std::result::Result::Err(kb_core::Error::new(
"token2022_validation_scenario_identity_invalid",
"Token-2022 validation scenario ids must be non-empty and unique",
));
}
if scenario.evidence.len() > crate::MAX_TOKEN2022_VALIDATION_EVIDENCE {
return std::result::Result::Err(kb_core::Error::new(
"token2022_validation_evidence_limit_exceeded",
format!(
"Token-2022 validation accepts at most {} evidence records per scenario",
crate::MAX_TOKEN2022_VALIDATION_EVIDENCE
),
));
}
for evidence in &scenario.evidence {
if evidence.kind.trim().is_empty() || evidence.value.trim().is_empty() {
return std::result::Result::Err(kb_core::Error::new(
"token2022_validation_evidence_invalid",
"Token-2022 validation evidence kind and value must be non-empty",
));
}
}
let completed = matches!(
scenario.status,
crate::Token2022ValidationStatus::Simulated
| crate::Token2022ValidationStatus::Submitted
| crate::Token2022ValidationStatus::Confirmed
| crate::Token2022ValidationStatus::Failed
);
if completed && scenario.evidence.is_empty() {
return std::result::Result::Err(kb_core::Error::new(
"token2022_validation_completed_without_evidence",
"A completed Token-2022 validation scenario must retain evidence",
));
}
if scenario.status == crate::Token2022ValidationStatus::Confirmed
&& (scenario.requires_canonical_hydration
|| scenario.requires_core_extraction
|| scenario.requires_decode_replay
|| scenario.requires_materialization
|| scenario.requires_second_replay)
&& !has_pipeline_evidence(scenario)
{
return std::result::Result::Err(kb_core::Error::new(
"token2022_validation_confirmed_without_pipeline_evidence",
"A confirmed end-to-end Token-2022 scenario must retain hydration, extraction, replay, materialization, and idempotence evidence",
));
}
}
return std::result::Result::Ok(());
}
fn has_pipeline_evidence(scenario: &crate::Token2022ValidationScenario) -> bool {
let kinds = scenario
.evidence
.iter()
.map(|evidence| return evidence.kind.as_str())
.collect::<std::collections::BTreeSet<&str>>();
if scenario.requires_canonical_hydration && !kinds.contains("canonical_hydration") {
return false;
}
if scenario.requires_core_extraction && !kinds.contains("core_extraction") {
return false;
}
if scenario.requires_decode_replay && !kinds.contains("decode_replay") {
return false;
}
if scenario.requires_materialization && !kinds.contains("materialization") {
return false;
}
if scenario.requires_second_replay && !kinds.contains("second_replay") {
return false;
}
return true;
}
#[cfg(test)]
mod tests {
fn scenario(status: crate::Token2022ValidationStatus) -> crate::Token2022ValidationScenario {
return crate::Token2022ValidationScenario {
id: "confidential_transfer_devnet".to_string(),
environment: crate::Token2022ValidationEnvironment::Devnet,
status,
requires_canonical_hydration: true,
requires_core_extraction: true,
requires_decode_replay: true,
requires_materialization: true,
requires_second_replay: true,
evidence: vec![
crate::Token2022ValidationEvidence {
kind: "signature".to_string(),
value: "signature".to_string(),
},
crate::Token2022ValidationEvidence {
kind: "canonical_hydration".to_string(),
value: "1".to_string(),
},
crate::Token2022ValidationEvidence {
kind: "core_extraction".to_string(),
value: "1".to_string(),
},
crate::Token2022ValidationEvidence {
kind: "decode_replay".to_string(),
value: "1".to_string(),
},
crate::Token2022ValidationEvidence {
kind: "materialization".to_string(),
value: "1".to_string(),
},
crate::Token2022ValidationEvidence {
kind: "second_replay".to_string(),
value: "0_new_outputs".to_string(),
},
],
};
}
#[test]
fn confirmed_end_to_end_scenario_requires_complete_pipeline_evidence() {
let report = crate::Token2022ValidationReport {
scenarios: vec![scenario(crate::Token2022ValidationStatus::Confirmed)],
};
assert!(crate::validate_token2022_validation_report(&report).is_ok());
let mut incomplete = scenario(crate::Token2022ValidationStatus::Confirmed);
incomplete.evidence.retain(|evidence| return evidence.kind != "second_replay");
assert!(
crate::validate_token2022_validation_report(&crate::Token2022ValidationReport {
scenarios: vec![incomplete]
})
.is_err()
);
}
#[test]
fn not_run_and_unavailable_scenarios_do_not_invent_evidence() {
let mut not_run = scenario(crate::Token2022ValidationStatus::NotRun);
not_run.evidence.clear();
let mut unavailable = scenario(crate::Token2022ValidationStatus::Unavailable);
unavailable.id = "permissioned_burn_devnet".to_string();
unavailable.evidence.clear();
assert!(
crate::validate_token2022_validation_report(&crate::Token2022ValidationReport {
scenarios: vec![not_run, unavailable]
})
.is_ok()
);
}
#[test]
fn duplicate_ids_empty_evidence_and_completed_without_evidence_fail_closed() {
let first = scenario(crate::Token2022ValidationStatus::Confirmed);
let duplicate = first.clone();
assert!(
crate::validate_token2022_validation_report(&crate::Token2022ValidationReport {
scenarios: vec![first, duplicate]
})
.is_err()
);
let mut failed = scenario(crate::Token2022ValidationStatus::Failed);
failed.evidence.clear();
assert!(
crate::validate_token2022_validation_report(&crate::Token2022ValidationReport {
scenarios: vec![failed]
})
.is_err()
);
}
#[test]
fn canonical_matrix_matches_compiled_inventory_and_observed_offline_evidence() {
let matrix = crate::load_token2022_validation_matrix();
assert!(matrix.is_ok());
let matrix = match matrix {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected matrix error: {error}"),
};
assert_eq!(matrix.scenarios.len(), 9);
assert_eq!(matrix.scenarios[0].status, crate::Token2022ValidationStatus::Confirmed);
assert_eq!(matrix.scenarios[0].evidence.len(), 4);
assert!(
matrix.scenarios[1..]
.iter()
.all(|scenario| return scenario.status == crate::Token2022ValidationStatus::NotRun)
);
}
#[test]
fn confirmed_matrix_scenario_requires_every_declared_evidence_kind() {
let matrix = crate::load_token2022_validation_matrix();
assert!(matrix.is_ok());
let mut matrix = match matrix {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("unexpected matrix error: {error}"),
};
matrix.scenarios[0].evidence.retain(|evidence| return evidence.kind != "clippy");
assert!(crate::validate_token2022_validation_matrix(&matrix).is_err());
}
}