|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| #![no_std]
|
| #![forbid(unsafe_code)]
|
| #![deny(missing_docs)]
|
|
|
| extern crate alloc;
|
|
|
| use alloc::vec::Vec;
|
| use core::fmt::Debug;
|
| use heapless::{Vec as HeaplessVec, Deque, FnvIndexMap};
|
|
|
| #[cfg(feature = "ffi")]
|
| use blake3;
|
|
|
|
|
| pub const MAX_HISTORY: usize = 256;
|
|
|
| pub const MAX_CANDIDATES: usize = 32;
|
|
|
| pub const ENTROPY_BOUND: f32 = 0.20;
|
|
|
| pub const MIN_BANDWIDTH_RATIO: u32 = 2;
|
|
|
|
|
| #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
| pub struct Ed25519PublicKey(pub [u8; 32]);
|
|
|
| impl Ed25519PublicKey {
|
|
|
| pub const fn new(bytes: [u8; 32]) -> Self {
|
| Self(bytes)
|
| }
|
|
|
|
|
| pub const fn capability_prefix(&self) -> u8 {
|
| self.0[0]
|
| }
|
| }
|
|
|
|
|
| #[derive(Clone, Debug, PartialEq)]
|
| pub struct Objective {
|
|
|
| pub target_space_hash: [u8; 32],
|
|
|
| pub constraints_hash: [u8; 32],
|
|
|
| pub success_metric_hash: [u8; 32],
|
|
|
| pub priority: u32,
|
|
|
| pub entropy_estimate: f32,
|
| }
|
|
|
| impl Objective {
|
|
|
| pub fn new(
|
| target_space_hash: [u8; 32],
|
| constraints_hash: [u8; 32],
|
| success_metric_hash: [u8; 32],
|
| priority: u32,
|
| entropy_estimate: f32,
|
| ) -> Option<Self> {
|
| if entropy_estimate > ENTROPY_BOUND || entropy_estimate < 0.0 {
|
| return None;
|
| }
|
| Some(Self {
|
| target_space_hash,
|
| constraints_hash,
|
| success_metric_hash,
|
| priority: priority.min(99),
|
| entropy_estimate,
|
| })
|
| }
|
|
|
|
|
| pub const fn capability_prefix(&self) -> u8 {
|
| self.target_space_hash[0]
|
| }
|
| }
|
|
|
|
|
| #[derive(Clone, Debug)]
|
| pub struct HistoryEntry {
|
| pub objective: Objective,
|
| pub result_hash: [u8; 32],
|
| pub proof_hash: [u8; 32],
|
| }
|
|
|
|
|
| #[derive(Clone, Debug)]
|
| pub struct OperatorState {
|
|
|
| pub history: Deque<HistoryEntry, MAX_HISTORY>,
|
|
|
| pub current_objective: Option<Objective>,
|
|
|
| pub entropy_budget: f32,
|
|
|
| pub trust_anchor: Ed25519PublicKey,
|
| }
|
|
|
| impl OperatorState {
|
|
|
| pub const fn new(trust_anchor: Ed25519PublicKey) -> Self {
|
| Self {
|
| history: Deque::new(),
|
| current_objective: None,
|
| entropy_budget: ENTROPY_BOUND,
|
| trust_anchor,
|
| }
|
| }
|
|
|
|
|
| pub fn sovereign_compliant(&self, obj: &Objective) -> bool {
|
| obj.capability_prefix() == self.trust_anchor.capability_prefix()
|
| }
|
| }
|
|
|
|
|
| #[derive(Clone, Copy, Debug, PartialEq)]
|
| pub struct ScoreWeights {
|
| pub alpha: f32,
|
| pub beta: f32,
|
| pub gamma: f32,
|
| pub delta: f32,
|
| }
|
|
|
| impl ScoreWeights {
|
|
|
| pub const DEFAULT: Self = Self {
|
| alpha: 0.4,
|
| beta: 0.2,
|
| gamma: 0.2,
|
| delta: 0.2,
|
| };
|
|
|
|
|
| pub const fn is_valid(&self) -> bool {
|
| let sum = self.alpha + self.beta + self.gamma + self.delta;
|
| (sum - 1.0).abs() < 1e-6
|
| }
|
| }
|
|
|
|
|
| pub struct DeterministicRng {
|
| state: [u32; 16],
|
| }
|
|
|
| impl DeterministicRng {
|
|
|
| pub fn new(seed: [u8; 32]) -> Self {
|
| let mut state = [0u32; 16];
|
|
|
| state[0..8].copy_from_slice(&[
|
| 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574,
|
| 0, 0, 0, 0,
|
| ]);
|
| for (i, chunk) in seed.chunks(4).enumerate() {
|
| state[4 + i] = u32::from_le_bytes(chunk.try_into().unwrap());
|
| }
|
| Self { state }
|
| }
|
|
|
|
|
| pub fn next_u32(&mut self) -> u32 {
|
| self.chacha20_block();
|
| self.state[0]
|
| }
|
|
|
|
|
| pub fn next_f32(&mut self) -> f32 {
|
| self.next_u32() as f32 / u32::MAX as f32
|
| }
|
|
|
|
|
| pub fn next_hash(&mut self) -> [u8; 32] {
|
| let mut out = [0u8; 32];
|
| for i in 0..8 {
|
| let word = self.next_u32();
|
| out[i*4..(i+1)*4].copy_from_slice(&word.to_le_bytes());
|
| }
|
| out
|
| }
|
|
|
|
|
| fn chacha20_block(&mut self) {
|
| let mut x = self.state;
|
| for _ in 0..10 {
|
| quarter_round(&mut x, 0, 4, 8, 12);
|
| quarter_round(&mut x, 1, 5, 9, 13);
|
| quarter_round(&mut x, 2, 6, 10, 14);
|
| quarter_round(&mut x, 3, 7, 11, 15);
|
| quarter_round(&mut x, 0, 5, 10, 15);
|
| quarter_round(&mut x, 1, 6, 11, 12);
|
| quarter_round(&mut x, 2, 7, 8, 13);
|
| quarter_round(&mut x, 3, 4, 9, 14);
|
| }
|
| for i in 0..16 {
|
| self.state[i] = self.state[i].wrapping_add(x[i]);
|
| }
|
| self.state[12] = self.state[12].wrapping_add(1);
|
| }
|
| }
|
|
|
| fn quarter_round(x: &mut [u32; 16], a: usize, b: usize, c: usize, d: usize) {
|
| x[a] = x[a].wrapping_add(x[b]); x[d] = (x[d] ^ x[a]).rotate_left(16);
|
| x[c] = x[c].wrapping_add(x[d]); x[b] = (x[b] ^ x[c]).rotate_left(12);
|
| x[a] = x[a].wrapping_add(x[b]); x[d] = (x[d] ^ x[a]).rotate_left(8);
|
| x[c] = x[c].wrapping_add(x[d]); x[b] = (x[b] ^ x[c]).rotate_left(7);
|
| }
|
|
|
|
|
| pub struct AutomatedOperator {
|
| state: OperatorState,
|
| rng: DeterministicRng,
|
| weights: ScoreWeights,
|
| }
|
|
|
| impl AutomatedOperator {
|
|
|
| pub fn new(seed: [u8; 32], trust_anchor: Ed25519PublicKey, weights: ScoreWeights) -> Self {
|
| assert!(weights.is_valid(), "ScoreWeights must sum to 1.0");
|
| Self {
|
| state: OperatorState::new(trust_anchor),
|
| rng: DeterministicRng::new(seed),
|
| weights,
|
| }
|
| }
|
|
|
|
|
| pub const fn state(&self) -> &OperatorState {
|
| &self.state
|
| }
|
|
|
|
|
| pub fn next_objective(&mut self) -> Option<Objective> {
|
| let mut candidates: HeaplessVec<Objective, MAX_CANDIDATES> = HeaplessVec::new();
|
|
|
| for _ in 0..MAX_CANDIDATES {
|
| if let Some(o) = self.generate_candidate() {
|
| if self.valid_objective(&o) {
|
| let _ = candidates.push(o);
|
| }
|
| }
|
| }
|
|
|
| if candidates.is_empty() {
|
|
|
| self.state.entropy_budget = (self.state.entropy_budget + 0.01).min(ENTROPY_BOUND);
|
| return None;
|
| }
|
|
|
|
|
| let best_idx = self.select_best(&candidates)?;
|
| let selected = candidates[best_idx].clone();
|
|
|
| self.state.current_objective = Some(selected.clone());
|
| Some(selected)
|
| }
|
|
|
|
|
| pub fn receive_result(&mut self, result_hash: [u8; 32], proof_hash: [u8; 32]) {
|
| if let Some(obj) = self.state.current_objective.take() {
|
| let entry = HistoryEntry {
|
| objective: obj,
|
| result_hash,
|
| proof_hash,
|
| };
|
| let _ = self.state.history.push_back(entry);
|
|
|
|
|
| self.state.entropy_budget = ENTROPY_BOUND - self.compute_history_entropy();
|
| }
|
| }
|
|
|
|
|
| fn generate_candidate(&mut self) -> Option<Objective> {
|
| let target_space_hash = self.rng.next_hash();
|
| let constraints_hash = self.rng.next_hash();
|
| let success_metric_hash = self.rng.next_hash();
|
| let priority = self.rng.next_u32() % 100;
|
| let entropy_estimate = self.rng.next_f32() * 0.15;
|
|
|
| Objective::new(
|
| target_space_hash,
|
| constraints_hash,
|
| success_metric_hash,
|
| priority,
|
| entropy_estimate,
|
| )
|
| }
|
|
|
|
|
| fn valid_objective(&self, o: &Objective) -> bool {
|
| o.entropy_estimate <= self.state.entropy_budget
|
| && self.state.sovereign_compliant(o)
|
| && self.verifiable_metric(o)
|
| }
|
|
|
|
|
| fn verifiable_metric(&self, _o: &Objective) -> bool {
|
| true
|
| }
|
|
|
|
|
| fn select_best(&self, candidates: &[Objective]) -> Option<usize> {
|
| candidates.iter()
|
| .enumerate()
|
| .max_by(|(_, a), (_, b)| {
|
| let sa = self.score(a);
|
| let sb = self.score(b);
|
| sa.partial_cmp(&sb).unwrap_or(core::cmp::Ordering::Equal)
|
| })
|
| .map(|(i, _)| i)
|
| }
|
|
|
|
|
| fn score(&self, o: &Objective) -> f32 {
|
| let novelty = self.novelty_estimate(o);
|
| let constraint_tightness = 1.0 - (o.entropy_estimate / ENTROPY_BOUND);
|
| let proof_complexity = self.estimate_proof_complexity(o);
|
| let sovereign_alignment = if self.state.sovereign_compliant(o) { 1.0 } else { 0.0 };
|
|
|
| self.weights.alpha * novelty
|
| + self.weights.beta * constraint_tightness
|
| + self.weights.gamma * proof_complexity
|
| + self.weights.delta * sovereign_alignment
|
| }
|
|
|
|
|
| fn novelty_estimate(&self, o: &Objective) -> f32 {
|
| if self.state.history.is_empty() {
|
| return 1.0;
|
| }
|
|
|
| let max_similarity = self.state.history.iter()
|
| .map(|h| self.structural_similarity(o, &h.objective))
|
| .fold(0.0f32, f32::max);
|
|
|
| 1.0 - max_similarity
|
| }
|
|
|
|
|
| fn structural_similarity(&self, a: &Objective, b: &Objective) -> f32 {
|
| let target_sim = hash_prefix_similarity(a.target_space_hash, b.target_space_hash);
|
| let constraint_sim = hash_prefix_similarity(a.constraints_hash, b.constraints_hash);
|
| let metric_sim = hash_prefix_similarity(a.success_metric_hash, b.success_metric_hash);
|
|
|
| (target_sim + constraint_sim + metric_sim) / 3.0
|
| }
|
|
|
|
|
| fn estimate_proof_complexity(&self, o: &Objective) -> f32 {
|
| o.entropy_estimate / ENTROPY_BOUND
|
| }
|
|
|
|
|
| fn compute_history_entropy(&self) -> f32 {
|
| if self.state.history.is_empty() {
|
| return 0.0;
|
| }
|
|
|
| let mut freq: FnvIndexMap<[u8; 4], u32, 64> = FnvIndexMap::new();
|
| for entry in &self.state.history {
|
| let prefix = [
|
| entry.objective.target_space_hash[0],
|
| entry.objective.target_space_hash[1],
|
| entry.objective.constraints_hash[0],
|
| entry.objective.constraints_hash[1],
|
| ];
|
| *freq.entry(prefix).or_insert(0) += 1;
|
| }
|
|
|
| let n = self.state.history.len() as f32;
|
| freq.values().fold(0.0f32, |acc, &count| {
|
| let p = count as f32 / n;
|
| acc - p * p.ln()
|
| })
|
| }
|
| }
|
|
|
|
|
| fn hash_prefix_similarity(a: [u8; 32], b: [u8; 32]) -> f32 {
|
| let matches = a.iter().zip(b.iter()).take(8).filter(|(x, y)| x == y).count();
|
| matches as f32 / 8.0
|
| }
|
|
|
| #[cfg(test)]
|
| mod tests {
|
| use super::*;
|
|
|
| #[test]
|
| fn test_determinism() {
|
| let seed = [42u8; 32];
|
| let anchor = Ed25519PublicKey([0xFF; 32]);
|
| let weights = ScoreWeights::DEFAULT;
|
|
|
| let mut op1 = AutomatedOperator::new(seed, anchor, weights);
|
| let mut op2 = AutomatedOperator::new(seed, anchor, weights);
|
|
|
| for _ in 0..100 {
|
| let o1 = op1.next_objective();
|
| let o2 = op2.next_objective();
|
| assert_eq!(o1, o2, "Determinism violated");
|
|
|
| if o1.is_some() {
|
| op1.receive_result([1u8; 32], [2u8; 32]);
|
| op2.receive_result([1u8; 32], [2u8; 32]);
|
| }
|
| }
|
| }
|
|
|
| #[test]
|
| fn test_entropy_bound() {
|
| let anchor = Ed25519PublicKey([0xFF; 32]);
|
| let mut op = AutomatedOperator::new([0u8; 32], anchor, ScoreWeights::DEFAULT);
|
|
|
| for _ in 0..1000 {
|
| if let Some(obj) = op.next_objective() {
|
| assert!(obj.entropy_estimate <= ENTROPY_BOUND);
|
| assert!(op.state.entropy_budget >= 0.0);
|
| assert!(op.state.entropy_budget <= ENTROPY_BOUND);
|
| op.receive_result([1u8; 32], [2u8; 32]);
|
| }
|
| }
|
| }
|
|
|
| #[test]
|
| fn test_sovereign_compliance() {
|
| let anchor = Ed25519PublicKey([0xFF; 32]);
|
| let mut op = AutomatedOperator::new([0u8; 32], anchor, ScoreWeights::DEFAULT);
|
|
|
| for _ in 0..1000 {
|
| if let Some(obj) = op.next_objective() {
|
| assert_eq!(obj.target_space_hash[0], 0xFF);
|
| op.receive_result([1u8; 32], [2u8; 32]);
|
| }
|
| }
|
| }
|
|
|
| #[test]
|
| fn test_novelty_no_collapse() {
|
| let mut op = AutomatedOperator::new([0u8; 32], Ed25519PublicKey([0xFF; 32]), ScoreWeights::DEFAULT);
|
| let mut novelties = Vec::new();
|
|
|
| for _ in 0..50 {
|
| if let Some(obj) = op.next_objective() {
|
| novelties.push(op.novelty_estimate(&obj));
|
| op.receive_result([1u8; 32], [2u8; 32]);
|
| }
|
| }
|
|
|
| let avg_novelty: f32 = novelties.iter().sum::<f32>() / novelties.len() as f32;
|
| assert!(avg_novelty > 0.30, "Mode collapse: novelty={}", avg_novelty);
|
| }
|
| } |