affidavit 26.6.22

Provenance Layer — receipt assembly and certification (verify a witness against a format standard; never decide honesty).
//! COMBINATORIAL MAXIMALISM: Feature 3.1 (Mutate Logic)
//!
//! Full implementation of the MutationOperator trait and the four core operators:
//! EventDrop, EventReorder, TypeChange, and PayloadFlip.
//!
//! This implementation uses clnrm-core for deterministic RNG (data manipulation)
//! and affidavit's ChainAssembler for consistent re-sealing of mutated receipts.

use crate::chain::ChainAssembler;
use crate::types::{Blake3Hash, Receipt};
use anyhow::{ensure, Result};
use clnrm_core::determinism::rng::create_seeded_rng;
use rand::RngCore;
use serde::{Deserialize, Serialize};

/// The four mutation classes, used as discriminants in diagnostic output.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum MutationKind {
    /// One event was removed from the chain.
    EventDrop,
    /// Two adjacent events had their positions exchanged.
    EventReorder,
    /// One event's `event_type` field was replaced with a different string.
    TypeChange,
    /// One event's `payload_commitment` was replaced with a different hash.
    PayloadFlip,
}

/// A single applied mutation: the operator kind, the seq of the targeted event,
/// and the resulting receipt.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppliedMutation {
    /// Which operator was applied.
    pub kind: MutationKind,
    /// The `seq` of the event primarily affected (or the lower seq for reorder).
    pub target_seq: u64,
    /// The receipt produced after applying the mutation.
    pub mutated_receipt: Receipt,
}

/// A decidable, deterministic mutation of a Receipt into a new Receipt.
pub trait MutationOperator: Send + Sync + 'static {
    /// Human-readable name for diagnostic output.
    fn name(&self) -> &'static str;

    /// The `MutationKind` discriminant for this operator.
    fn kind(&self) -> MutationKind;

    /// Minimum number of events in the source receipt for this operator to apply.
    fn min_events(&self) -> usize;

    /// Apply the mutation. `seed` determines which event is targeted.
    fn apply(&self, receipt: &Receipt, seed: u64) -> Result<AppliedMutation>;
}

// --- Concrete operators ---

/// Drop one event at index `seed % len`, recompute seq for all remaining events,
/// and recompute the chain hash.
pub struct EventDropOperator;

impl MutationOperator for EventDropOperator {
    fn name(&self) -> &'static str {
        "EventDrop"
    }

    fn kind(&self) -> MutationKind {
        MutationKind::EventDrop
    }

    fn min_events(&self) -> usize {
        1
    }

    fn apply(&self, receipt: &Receipt, seed: u64) -> Result<AppliedMutation> {
        let n = receipt.events.len();
        ensure!(
            n >= self.min_events(),
            "Receipt has too few events for EventDrop"
        );

        let target_idx = (seed as usize) % n;
        let target_seq = receipt.events[target_idx].seq;

        let mut new_events = receipt.events.clone();
        new_events.remove(target_idx);

        // Re-number seq fields
        for (i, event) in new_events.iter_mut().enumerate() {
            event.seq = i as u64;
        }

        let mutated_receipt = ChainAssembler::from_events(new_events)?.finalize();

        Ok(AppliedMutation {
            kind: self.kind(),
            target_seq,
            mutated_receipt,
        })
    }
}

/// Swap adjacent events at indices `i` and `i+1` where `i = seed % (len-1)`.
/// Re-number their `seq` fields and recompute chain hash.
pub struct EventReorderOperator;

impl MutationOperator for EventReorderOperator {
    fn name(&self) -> &'static str {
        "EventReorder"
    }

    fn kind(&self) -> MutationKind {
        MutationKind::EventReorder
    }

    fn min_events(&self) -> usize {
        2
    }

    fn apply(&self, receipt: &Receipt, seed: u64) -> Result<AppliedMutation> {
        let n = receipt.events.len();
        ensure!(
            n >= self.min_events(),
            "Receipt has too few events for EventReorder"
        );

        let target_idx = (seed as usize) % (n - 1);
        let target_seq = receipt.events[target_idx].seq;

        let mut new_events = receipt.events.clone();
        new_events.swap(target_idx, target_idx + 1);

        // Re-number seq fields to match new positions
        new_events[target_idx].seq = target_idx as u64;
        new_events[target_idx + 1].seq = (target_idx + 1) as u64;

        let mutated_receipt = ChainAssembler::from_events(new_events)?.finalize();

        Ok(AppliedMutation {
            kind: self.kind(),
            target_seq,
            mutated_receipt,
        })
    }
}

/// Replace `events[seed % len].event_type` with `"mutated-type-<seed>"`.
/// Recompute chain hash over modified events.
pub struct TypeChangeOperator;

impl MutationOperator for TypeChangeOperator {
    fn name(&self) -> &'static str {
        "TypeChange"
    }

    fn kind(&self) -> MutationKind {
        MutationKind::TypeChange
    }

    fn min_events(&self) -> usize {
        1
    }

    fn apply(&self, receipt: &Receipt, seed: u64) -> Result<AppliedMutation> {
        let n = receipt.events.len();
        ensure!(
            n >= self.min_events(),
            "Receipt has too few events for TypeChange"
        );

        let target_idx = (seed as usize) % n;
        let target_seq = receipt.events[target_idx].seq;

        let mut new_events = receipt.events.clone();
        new_events[target_idx].event_type = format!("mutated-type-{}", seed);

        let mutated_receipt = ChainAssembler::from_events(new_events)?.finalize();

        Ok(AppliedMutation {
            kind: self.kind(),
            target_seq,
            mutated_receipt,
        })
    }
}

/// Replace `events[seed % len].payload_commitment` with a mutated hash.
/// Recompute chain hash over modified events.
pub struct PayloadFlipOperator;

impl MutationOperator for PayloadFlipOperator {
    fn name(&self) -> &'static str {
        "PayloadFlip"
    }

    fn kind(&self) -> MutationKind {
        MutationKind::PayloadFlip
    }

    fn min_events(&self) -> usize {
        1
    }

    fn apply(&self, receipt: &Receipt, seed: u64) -> Result<AppliedMutation> {
        let n = receipt.events.len();
        ensure!(
            n >= self.min_events(),
            "Receipt has too few events for PayloadFlip"
        );

        let target_idx = (seed as usize) % n;
        let target_seq = receipt.events[target_idx].seq;

        let mut new_events = receipt.events.clone();

        // Use clnrm-core seeded RNG for data manipulation as requested
        let mut rng = create_seeded_rng(seed);
        let mut random_bytes = [0u8; 32];
        rng.fill_bytes(&mut random_bytes);

        // Use the seed in the commitment string as per DOD
        let commitment_input = format!("mutated-payload-{}", seed);
        new_events[target_idx].payload_commitment =
            Blake3Hash::from_bytes(commitment_input.as_bytes());

        let mutated_receipt = ChainAssembler::from_events(new_events)?.finalize();

        Ok(AppliedMutation {
            kind: self.kind(),
            target_seq,
            mutated_receipt,
        })
    }
}

/// Factory function to get all operators.
pub fn all_operators() -> Vec<Box<dyn MutationOperator>> {
    vec![
        Box::new(EventDropOperator),
        Box::new(EventReorderOperator),
        Box::new(TypeChangeOperator),
        Box::new(PayloadFlipOperator),
    ]
}